diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cdef203bf5501e..d158ec61a7ee31 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM containerbase/node:v14.18.1@sha256:b720748f7a35570e402a8741f37a5bd54ad485dd6b4aeaff04027ad761b1306d +FROM containerbase/node:14.18.2@sha256:39288a62b456a381b12cf63aa207ec0b356a05410e95ee1b3fbbc2e1933b1ebc # renovate: datasource=npm diff --git a/.eslintrc.js b/.eslintrc.js index 19699d6632e416..190ad0dec7ba8c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,14 +5,13 @@ module.exports = { }, plugins: ['@renovate'], extends: [ - 'airbnb-typescript/base', + 'eslint:recommended', 'plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript', 'plugin:jest/recommended', 'plugin:jest/style', // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin/src/configs - 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:promise/recommended', @@ -34,15 +33,15 @@ module.exports = { 'import/named': 0, 'import/namespace': 0, 'import/no-named-as-default-member': 0, + 'import/prefer-default-export': 0, // no benefit // other rules - 'import/prefer-default-export': 0, // no benefit - 'no-restricted-syntax': 0, - 'no-await-in-loop': 0, - 'prefer-destructuring': 0, - 'prefer-template': 0, - 'no-underscore-dangle': 0, + 'consistent-return': 'error', + eqeqeq: 'error', + 'no-console': 'error', 'no-negated-condition': 'error', + 'no-param-reassign': 'error', + 'no-template-curly-in-string': 'error', 'sort-imports': [ 'error', { @@ -95,6 +94,7 @@ module.exports = { // TODO: fix me '@typescript-eslint/no-unsafe-return': 0, '@typescript-eslint/no-unsafe-call': 0, + '@typescript-eslint/no-unsafe-argument': 0, // thousands of errors :-/ '@typescript-eslint/restrict-template-expressions': [ 1, @@ -102,7 +102,13 @@ module.exports = { ], '@typescript-eslint/restrict-plus-operands': 2, - '@typescript-eslint/naming-convention': 2, + '@typescript-eslint/naming-convention': [ + 2, + { + selector: 'enumMember', + format: ['PascalCase'], + }, + ], '@typescript-eslint/unbound-method': 2, '@typescript-eslint/ban-types': 2, @@ -120,6 +126,7 @@ module.exports = { jest: true, }, rules: { + 'no-template-curly-in-string': 0, 'prefer-destructuring': 0, 'prefer-promise-reject-errors': 0, 'import/no-dynamic-require': 0, diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index fb5b8bdb527ab7..83d409b4d49947 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -24,6 +24,7 @@ body: label: Is this a feature you are interested in implementing yourself? options: - 'Yes' + - 'Maybe' - 'No' validations: required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da98ab06ec03b2..4e82a7201f944f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,26 +37,22 @@ jobs: matrix: os: [ubuntu-latest] node-version: [14] - python-version: [3.9] java-version: [11] # skip macOS and Windows test on pull requests without 'ci:fulltest' label include: >- ${{ fromJSON((github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:fulltest')) && '[{ "os": "macos-latest", "node-version": 14, - "python-version": 3.9, "java-version": 11 }, { "os": "windows-latest", "node-version": 14, - "python-version": 3.9, "java-version": 11 }]' || '[]') }} env: coverage: ${{ matrix.os == 'ubuntu-latest' && matrix.node-version == 14 }} NODE_VERSION: ${{ matrix.node-version }} - PYTHON_VERSION: ${{ matrix.python-version }} JAVA_VERSION: ${{ matrix.java-version }} # skip Java tests on pull requests without 'ci:fulltest' label SKIP_JAVA_TESTS: ${{ matrix.node-version != 14 || (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci:fulltest')) }} @@ -67,19 +63,14 @@ jobs: fetch-depth: 2 - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@270253e841af726300e85d718a5f606959b2903c # renovate: tag=v2.4.1 + uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 # renovate: tag=v2.5.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@dc73133d4da04e56a135ae2246682783cc7c7cb6 # renovate: tag=v2.2.2 - with: - python-version: ${{ env.PYTHON_VERSION }} - - name: Set up Java ${{ env.JAVA_VERSION }} if: env.SKIP_JAVA_TESTS == 'false' - uses: actions/setup-java@8db439b6b47e5e12312bf036760bbaa6893481ac # renovate: tag=v2.3.1 + uses: actions/setup-java@5f00602cd1b2819185d88dc7a1b1985f598c6705 # renovate: tag=v2.4.0 with: java-version: ${{ env.JAVA_VERSION }} distribution: 'adopt' @@ -96,7 +87,6 @@ jobs: npm config set scripts-prepend-node-path true git --version echo "Node $(node --version)" - python --version echo "Yarn $(yarn --version)" - name: Installing dependencies @@ -129,7 +119,7 @@ jobs: fetch-depth: 2 - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@270253e841af726300e85d718a5f606959b2903c # renovate: tag=v2.4.1 + uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 # renovate: tag=v2.5.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn @@ -154,6 +144,7 @@ jobs: yarn prettier yarn markdown-lint yarn git-check + yarn doc-fence-check - name: Test schema run: yarn test-schema @@ -161,6 +152,9 @@ jobs: - name: Type check run: yarn type-check + - name: Null check + run: yarn null-check + release: needs: [lint, test] if: github.event_name != 'pull_request' @@ -175,7 +169,7 @@ jobs: fetch-depth: 0 - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@270253e841af726300e85d718a5f606959b2903c # renovate: tag=v2.4.1 + uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 # renovate: tag=v2.5.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a78d12949ccbed..fa4a4c870605a8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,7 +26,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5581e08a65fc3811c3ac78939dd59e7a8adbf003 # renovate: tag=v1.0.22 + uses: github/codeql-action/init@546b30f35ae5a3db0e0be1843008c2224f71c3b0 # renovate: tag=v1.0.25 with: config-file: ./.github/codeql/codeql-config.yml @@ -36,7 +36,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@5581e08a65fc3811c3ac78939dd59e7a8adbf003 # renovate: tag=v1.0.22 + uses: github/codeql-action/autobuild@546b30f35ae5a3db0e0be1843008c2224f71c3b0 # renovate: tag=v1.0.25 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -50,4 +50,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5581e08a65fc3811c3ac78939dd59e7a8adbf003 # renovate: tag=v1.0.22 + uses: github/codeql-action/analyze@546b30f35ae5a3db0e0be1843008c2224f71c3b0 # renovate: tag=v1.0.25 diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 6fbc1c81d57696..aeae97e96cf264 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -40,7 +40,7 @@ jobs: ref: ${{ env.GIT_SHA }} - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@270253e841af726300e85d718a5f606959b2903c # renovate: tag=v2.4.1 + uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 # renovate: tag=v2.5.0 with: node-version: ${{ env.NODE_VERSION }} cache: yarn diff --git a/data/extract.py b/data/extract.py deleted file mode 100644 index cf53fdc5725059..00000000000000 --- a/data/extract.py +++ /dev/null @@ -1,54 +0,0 @@ -import sys -import json -import os -from os.path import basename - -if sys.version_info[:2] >= (3, 3): - from importlib.machinery import SourceFileLoader - def load_source(name, path): - if not os.path.exists(path): - return {} - return vars(SourceFileLoader('mod', path).load_module()) -else: - import imp - def load_source(name, path): - if not os.path.exists(path): - return {} - return vars(imp.load_source('mod', path)) - -try: - import setuptools -except ImportError: - class setuptools: - def setup(): - pass - -import distutils.core - -try: - from unittest import mock -except ImportError: - # for python3.3+ - import mock - -@mock.patch.object(setuptools, 'setup') -@mock.patch.object(distutils.core, 'setup') -def invoke(mock1, mock2): - # Inserting the parent directory of the target setup.py in Python import path: - sys.path.append(os.getcwd()) - # This is setup.py which calls setuptools.setup - load_source('_target_setup_', basename(sys.argv[-1])) - # called arguments are in `mock_setup.call_args` - call_args = mock1.call_args or mock2.call_args - - if call_args: - # get only install_requires and extras_require arguments - kwargs = { - k: v for k, v in call_args[1].items() - if k in ('install_requires', 'extras_require') - } - # save report.json - with open('renovate-pip_setup-report.json', 'w', encoding='utf-8') as f: - json.dump(kwargs, f, ensure_ascii=False, indent=2) - -invoke() diff --git a/data/node-js-schedule.json b/data/node-js-schedule.json index 5ed016d65bf202..87b2c1977d1fff 100644 --- a/data/node-js-schedule.json +++ b/data/node-js-schedule.json @@ -88,7 +88,7 @@ "lts": "2021-10-26", "maintenance": "2022-10-18", "end": "2024-04-30", - "codename": "" + "codename": "Gallium" }, "v17": { "start": "2021-10-19", diff --git a/docs/development/configuration.md b/docs/development/configuration.md index 333a9b4de407c2..d3bfca3b7f6edd 100644 --- a/docs/development/configuration.md +++ b/docs/development/configuration.md @@ -53,11 +53,10 @@ If you add a `renovate.json` file to the root of your repository, you can use th If you add configuration options to your `package.json` then these will override any other settings above. ```json -"renovate": { - "labels": [ - "upgrade", - "bot" - ] +{ + "renovate": { + "labels": ["upgrade", "bot"] + } } ``` diff --git a/docs/development/issue-labeling.md b/docs/development/issue-labeling.md index ddf729ba23a1a4..83b17fc3de2b98 100644 --- a/docs/development/issue-labeling.md +++ b/docs/development/issue-labeling.md @@ -43,7 +43,7 @@ Most issues should have a label relating to either a platform, manager, datasour Use these to label the status of an issue. For example, use `status:requirements` to mean that an issue is not yet ready for development to begin. If we need the original poster or somebody else to respond to a query of ours, apply the `status:waiting-on-response` label. -All open issues should have some `status:*` label applied, and [this search](https://github.com/renovatebot/renovate/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+-label%3Astatus%3Arequirements+-label%3Astatus%3Aready+-label%3Astatus%3Ain-progress+-label%3Astatus%3Ablocked) can identify any which are missing a status label. +All open issues should have some `status:*` label applied, and [this search](https://github.com/renovatebot/renovate/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+-label%3Astatus%3Arequirements+-label%3Astatus%3Aready+-label%3Astatus%3Ain-progress+-label%3Astatus%3Ablocked+-label%3Astatus%3Awaiting-on-response+) can identify any which are missing a status label. ### Type of issue diff --git a/docs/development/local-development.md b/docs/development/local-development.md index 4d848f83163f63..8f1dbc209c0b4f 100644 --- a/docs/development/local-development.md +++ b/docs/development/local-development.md @@ -14,7 +14,6 @@ You need the following dependencies for local development: - Node.js `>=14.15.4` - Yarn `^1.22.5` - C++ compiler -- Python `^3.9` - Java between `8` and `12` We support Node.js versions according to the [Node.js release schedule](https://github.com/nodejs/Release#release-schedule). @@ -31,7 +30,7 @@ curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update -sudo apt-get install -y git python-minimal build-essential nodejs yarn default-jre-headless +sudo apt-get install -y git build-essential nodejs yarn default-jre-headless ``` You can also use [SDKMAN](https://sdkman.io/) to manage Java versions. @@ -43,7 +42,7 @@ If you already installed a component, skip the corresponding step. - Install [Git](https://git-scm.com/downloads). Make sure you've [configured your username and email](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) - Install [Node.js LTS](https://nodejs.org/en/download/) -- In an Administrator PowerShell prompt, run `npm install -global npm` and then `npm --add-python-to-path='true' --debug install --global windows-build-tools` +- In an Administrator PowerShell prompt, run `npm install -global npm` and then `npm --debug install --global windows-build-tools` - Install [Yarn](https://yarnpkg.com/lang/en/docs/install/#windows-stable) - Install Java, e.g. from [AdoptOpenJDK](https://adoptopenjdk.net/?variant=openjdk11) or any other distribution @@ -53,8 +52,6 @@ If you already installed a component, skip the corresponding step. PS C:\Windows\system32> git --version PS C:\Windows\system32> node --version PS C:\Windows\system32> yarn --version - PS C:\Windows\system32> python --version - PS C:\Windows\system32> python -c "from unittest import mock; print(mock.__version__)" PS C:\Windows\system32> java -version ``` @@ -80,7 +77,7 @@ To ensure everything is working properly on your end, you must: 1. Verify all tests pass and have 100% test coverage, by running `yarn test` 1. Verify the installation by running `yarn start`. You must see this error: `You must configure a GitHub personal access token` -You only need to do these 5 steps this one time. +You only need to do these steps once. Before you submit a pull request you should: @@ -181,10 +178,10 @@ It's usually easier to have the logs in a file that you can open with a text edi You can use a command like this to put the log messages in a file: ``` -rm -f debug.log && yarn start myaccount/therepo --log-level=debug > debug.log +LOG_LEVEL=debug yarn start myaccount/therepo > debug.log ``` -The example command will delete any existing `debug.log` and then save Renovate's output to a new `debug.log` file. +The example command will redirect/save Renovate's output to the `debug.log` file (and overwrite `debug.log` if it already exists). ### Adding configuration options diff --git a/docs/development/shareable-configs.md b/docs/development/shareable-configs.md index 1add3fc2f1f00a..3f665feac1a548 100644 --- a/docs/development/shareable-configs.md +++ b/docs/development/shareable-configs.md @@ -54,40 +54,44 @@ You can set a Git tag (like a SemVer) to use a specific release of your shared c #### GitHub -| name | example use | preset | resolves as | filename | Git tag | -| ------------------------------------------- | ------------------------------- | --------- | ---------------------------- | --------------- | -------------- | -| GitHub default | `github>abc/foo` | `default` | `https://github.com/abc/foo` | `default.json` | Default branch | -| GitHub with preset name | `github>abc/foo:xyz` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | Default branch | -| GitHub default with a tag | `github>abc/foo#1.5.4` | `default` | `https://github.com/abc/foo` | `default.json` | `1.5.4` | -| GitHub with preset name with a tag | `github>abc/foo:xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | `1.5.4` | -| GitHub with preset name and path with a tag | `github>abc/foo:path/xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| name | example use | preset | resolves as | filename | Git tag | +| ------------------------------------------- | -------------------------------- | --------- | ---------------------------- | --------------- | -------------- | +| GitHub default | `github>abc/foo` | `default` | `https://github.com/abc/foo` | `default.json` | Default branch | +| GitHub with preset name | `github>abc/foo:xyz` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | Default branch | +| GitHub default with a tag | `github>abc/foo#1.5.4` | `default` | `https://github.com/abc/foo` | `default.json` | `1.5.4` | +| GitHub with preset name with a tag | `github>abc/foo:xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | `1.5.4` | +| GitHub with preset name and path with a tag | `github>abc/foo//path/xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| GitHub with subpreset name and tag | `github>abc/foo:xyz/sub#1.5.4` | `sub` | `https://github.com/abc/foo` | `xyz.json` | `1.5.4` | #### GitLab -| name | example use | preset | resolves as | filename | Git tag | -| ------------------------------------------- | ------------------------------- | --------- | ---------------------------- | --------------- | -------------- | -| GitLab default | `gitlab>abc/foo` | `default` | `https://gitlab.com/abc/foo` | `default.json` | Default branch | -| GitLab with preset name | `gitlab>abc/foo:xyz` | `xyz` | `https://gitlab.com/abc/foo` | `xyz.json` | Default branch | -| GitLab default with a tag | `gitlab>abc/foo#1.5.4` | `default` | `https://gitlab.com/abc/foo` | `default.json` | `1.5.4` | -| GitLab with preset name with a tag | `gitlab>abc/foo:xyz#1.5.4` | `xyz` | `https://gitlab.com/abc/foo` | `xyz.json` | `1.5.4` | -| GitLab with preset name and path with a tag | `gitlab>abc/foo:path/xyz#1.5.4` | `xyz` | `https://gitlab.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| name | example use | preset | resolves as | filename | Git tag | +| ------------------------------------------- | -------------------------------- | --------- | ---------------------------- | --------------- | -------------- | +| GitLab default | `gitlab>abc/foo` | `default` | `https://gitlab.com/abc/foo` | `default.json` | Default branch | +| GitLab with preset name | `gitlab>abc/foo:xyz` | `xyz` | `https://gitlab.com/abc/foo` | `xyz.json` | Default branch | +| GitLab default with a tag | `gitlab>abc/foo#1.5.4` | `default` | `https://gitlab.com/abc/foo` | `default.json` | `1.5.4` | +| GitLab with preset name with a tag | `gitlab>abc/foo:xyz#1.5.4` | `xyz` | `https://gitlab.com/abc/foo` | `xyz.json` | `1.5.4` | +| GitLab with preset name and path with a tag | `gitlab>abc/foo//path/xyz#1.5.4` | `xyz` | `https://gitlab.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| GitLab with subpreset name and tag | `gitlab>abc/foo:xyz/sub#1.5.4` | `sub` | `https://gitlab.com/abc/foo` | `xyz.json` | `1.5.4` | #### Gitea -| name | example use | preset | resolves as | filename | Git tag | -| ------------------------------------------ | ------------------------------ | --------- | --------------------------- | --------------- | -------------- | -| Gitea default | `gitea>abc/foo` | `default` | `https://gitea.com/abc/foo` | `default.json` | Default branch | -| Gitea with preset name | `gitea>abc/foo:xyz` | `xyz` | `https://gitea.com/abc/foo` | `xyz.json` | Default branch | -| Gitea default with a tag | `gitea>abc/foo#1.5.4` | `default` | `https://gitea.com/abc/foo` | `default.json` | `1.5.4` | -| Gitea with preset name with a tag | `gitea>abc/foo:xyz#1.5.4` | `xyz` | `https://gitea.com/abc/foo` | `xyz.json` | `1.5.4` | -| Gitea with preset name and path with a tag | `gitea>abc/foo:path/xyz#1.5.4` | `xyz` | `https://gitea.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| name | example use | preset | resolves as | filename | Git tag | +| ------------------------------------------ | ------------------------------- | --------- | --------------------------- | --------------- | -------------- | +| Gitea default | `gitea>abc/foo` | `default` | `https://gitea.com/abc/foo` | `default.json` | Default branch | +| Gitea with preset name | `gitea>abc/foo:xyz` | `xyz` | `https://gitea.com/abc/foo` | `xyz.json` | Default branch | +| Gitea default with a tag | `gitea>abc/foo#1.5.4` | `default` | `https://gitea.com/abc/foo` | `default.json` | `1.5.4` | +| Gitea with preset name with a tag | `gitea>abc/foo:xyz#1.5.4` | `xyz` | `https://gitea.com/abc/foo` | `xyz.json` | `1.5.4` | +| Gitea with preset name and path with a tag | `gitea>abc/foo//path/xyz#1.5.4` | `xyz` | `https://gitea.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| Gitea with subpreset name and tag | `gitea>abc/foo:xyz/sub#1.5.4` | `sub` | `https://gitea.com/abc/foo` | `xyz.json` | `1.5.4` | #### Self-hosted Git -| name | example use | preset | resolves as | filename | Git tag | -| ------------------------------------------ | ------------------------------ | --------- | ------------------------------------ | --------------- | -------------- | -| Local default | `local>abc/foo` | `default` | `https://github.company.com/abc/foo` | `default.json` | Default branch | -| Local with preset path | `local>abc/foo:path/xyz` | `default` | `https://github.company.com/abc/foo` | `path/xyz.json` | Default branch | -| Local default with a tag | `local>abc/foo#1.5.4` | `default` | `https://github.company.com/abc/foo` | `default.json` | `1.5.4` | -| Local with preset name with a tag | `local>abc/foo:xyz#1.5.4` | `default` | `https://github.company.com/abc/foo` | `xyz.json` | `1.5.4` | -| Local with preset name and path with a tag | `local>abc/foo:path/xyz#1.5.4` | `default` | `https://github.company.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| name | example use | preset | resolves as | filename | Git tag | +| ------------------------------------------ | ------------------------------- | --------- | ------------------------------------ | --------------- | -------------- | +| Local default | `local>abc/foo` | `default` | `https://github.company.com/abc/foo` | `default.json` | Default branch | +| Local with preset path | `local>abc/foo:path/xyz` | `default` | `https://github.company.com/abc/foo` | `path/xyz.json` | Default branch | +| Local default with a tag | `local>abc/foo#1.5.4` | `default` | `https://github.company.com/abc/foo` | `default.json` | `1.5.4` | +| Local with preset name with a tag | `local>abc/foo:xyz#1.5.4` | `default` | `https://github.company.com/abc/foo` | `xyz.json` | `1.5.4` | +| Local with preset name and path with a tag | `local>abc/foo//path/xyz#1.5.4` | `default` | `https://github.company.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| Local with subpreset name and tag | `local>abc/foo:xyz/sub#1.5.4` | `sub` | `https://github.company.com/abc/foo` | `xyz.json` | `1.5.4` | diff --git a/docs/usage/config-presets.md b/docs/usage/config-presets.md index f1e5d450e3741d..2d304497d7bbf7 100644 --- a/docs/usage/config-presets.md +++ b/docs/usage/config-presets.md @@ -45,13 +45,14 @@ You can set a Git tag (like a SemVer) to use a specific release of your shared c ### GitHub -| name | example use | preset | resolves as | filename | Git tag | -| ------------------------------------------- | ------------------------------- | --------- | ---------------------------- | --------------- | -------------- | -| GitHub default | `github>abc/foo` | `default` | `https://github.com/abc/foo` | `default.json` | Default branch | -| GitHub with preset name | `github>abc/foo:xyz` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | Default branch | -| GitHub default with a tag | `github>abc/foo#1.5.4` | `default` | `https://github.com/abc/foo` | `default.json` | `1.5.4` | -| GitHub with preset name with a tag | `github>abc/foo:xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | `1.5.4` | -| GitHub with preset name and path with a tag | `github>abc/foo:path/xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| name | example use | preset | resolves as | filename | Git tag | +| ------------------------------------------- | -------------------------------- | --------- | ---------------------------- | --------------- | -------------- | +| GitHub default | `github>abc/foo` | `default` | `https://github.com/abc/foo` | `default.json` | Default branch | +| GitHub with preset name | `github>abc/foo:xyz` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | Default branch | +| GitHub default with a tag | `github>abc/foo#1.5.4` | `default` | `https://github.com/abc/foo` | `default.json` | `1.5.4` | +| GitHub with preset name with a tag | `github>abc/foo:xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `xyz.json` | `1.5.4` | +| GitHub with preset name and path with a tag | `github>abc/foo//path/xyz#1.5.4` | `xyz` | `https://github.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| GitHub with subpreset name and tag | `github>abc/foo:xyz/sub#1.5.4` | `sub` | `https://github.com/abc/foo` | `xyz.json` | `1.5.4` | ### GitLab @@ -62,6 +63,7 @@ You can set a Git tag (like a SemVer) to use a specific release of your shared c | GitLab default with a tag | `gitlab>abc/foo#1.5.4` | `default` | `https://gitlab.com/abc/foo` | `default.json` | `1.5.4` | | GitLab with preset name with a tag | `gitlab>abc/foo:xyz#1.5.4` | `xyz` | `https://gitlab.com/abc/foo` | `xyz.json` | `1.5.4` | | GitLab with preset name and path with a tag | `gitlab>abc/foo:path/xyz#1.5.4` | `xyz` | `https://gitlab.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| GitLab with subpreset name and tag | `gitlab>abc/foo:xyz/sub#1.5.4` | `sub` | `https://gitlab.com/abc/foo` | `xyz.json` | `1.5.4` | ### Gitea @@ -72,6 +74,7 @@ You can set a Git tag (like a SemVer) to use a specific release of your shared c | Gitea default with a tag | `gitea>abc/foo#1.5.4` | `default` | `https://gitea.com/abc/foo` | `default.json` | `1.5.4` | | Gitea with preset name with a tag | `gitea>abc/foo:xyz#1.5.4` | `xyz` | `https://gitea.com/abc/foo` | `xyz.json` | `1.5.4` | | Gitea with preset name and path with a tag | `gitea>abc/foo:path/xyz#1.5.4` | `xyz` | `https://gitea.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| Gitea with subpreset name and tag | `gitea>abc/foo:xyz/sub#1.5.4` | `sub` | `https://gitea.com/abc/foo` | `xyz.json` | `1.5.4` | ### Self-hosted Git @@ -82,6 +85,7 @@ You can set a Git tag (like a SemVer) to use a specific release of your shared c | Local default with a tag | `local>abc/foo#1.5.4` | `default` | `https://github.company.com/abc/foo` | `default.json` | `1.5.4` | | Local with preset name with a tag | `local>abc/foo:xyz#1.5.4` | `xyz` | `https://github.company.com/abc/foo` | `xyz.json` | `1.5.4` | | Local with preset name and path with a tag | `local>abc/foo:path/xyz#1.5.4` | `xyz` | `https://github.company.com/abc/foo` | `path/xyz.json` | `1.5.4` | +| Local with subpreset name and tag | `local>abc/foo:xyz/sub#1.5.4` | `sub` | `https://github.company.com/abc/foo` | `xyz.json` | `1.5.4` | Note that you can't combine the path and sub-preset syntaxes. This means that anything in the form `provider>owner/repo//path/to/file:subsubpreset` is not supported. @@ -128,28 +132,24 @@ You can find the Renovate team's preset configs at the "Config Presets" section If you browse the "default" presets, you will see some that contain parameters, e.g.: ```json - "labels": { - "description": "Apply labels {{arg0}} and {{arg1}} to PRs", - "labels": [ - "{{arg0}}", - "{{arg1}}" - ] - }, - "assignee": { - "description": "Assign PRs to {{arg0}}", - "assignees": [ - "{{arg0}}" - ] - }, +{ + "labels": { + "description": "Apply labels {{arg0}} and {{arg1}} to PRs", + "labels": ["{{arg0}}", "{{arg1}}"] + }, + "assignee": { + "description": "Assign PRs to {{arg0}}", + "assignees": ["{{arg0}}"] + } +} ``` Here is how you would use these in your Renovate config: ```json - "extends": [ - ":labels(dependencies,devops)", - ":assignee(rarkins)" - ] +{ + "extends": [":labels(dependencies,devops)", ":assignee(rarkins)"] +} ``` In short, the number of `{{argx}}` parameters in the definition is how many parameters you need to provide. @@ -170,7 +170,9 @@ To host your preset config on GitHub: - In other repos, reference it in an extends array like "github>owner/name", for example: ```json +{ "extends": ["github>rarkins/renovate-config"] +} ``` From then on Renovate will use the Renovate config from the preset repo's default branch. @@ -259,7 +261,6 @@ For example: { "name": "renovate-config-fastcore", "version": "0.0.1", - ... "renovate-config": { "default": { "extends": ["config:base", "schedule:nonOfficeHours"] @@ -271,7 +272,9 @@ For example: Then in each of your repositories you can add your Renovate config like: ```json +{ "extends": ["fastcore"] +} ``` Any repository including this config will then adopt the rules of the default `library` preset but schedule it on weeknights or weekends. @@ -279,5 +282,7 @@ Any repository including this config will then adopt the rules of the default `l Note: if you prefer to publish using the namespace `@fastcore/renovate-config` then you would use the `@` prefix instead: ```json +{ "extends": ["@fastcore"] +} ``` diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 049774b458ac85..971d7e46895a3c 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -377,6 +377,13 @@ Set to `null` (not recommended) to fully omit `--ignore-platform-reqs/--ignore-p This requires the Renovate image to be fully compatible with your Composer platform requirements in order for the Composer invocation to succeed, otherwise Renovate will fail to create the updated lock file. The Composer output should inform you about the reasons the update failed. +## confidential + +If enabled, all issues created by Renovate are set as confidential, even in a public repository. +**Note:** the Dependency Dashboard issue will also be confidential. +By default issues created by Renovate are visible to all users. +This option is applicable to GitLab only. + ## configWarningReuseIssue Renovate's default behavior is to reuse/reopen a single Config Warning issue in each repository so as to keep the "noise" down. @@ -385,7 +392,7 @@ Configure this option to `false` if you prefer Renovate to open a new issue when ## constraints -Constraints are used in package managers which use third party tools to update "artifacts" like lock files or checksum files. +Constraints are used in package managers which use third-party tools to update "artifacts" like lock files or checksum files. Typically, the constraint is detected automatically by Renovate from files within the repository and there is no need to manually configure it. Manually specifying constraints is supported for `ruby`, `bundler`, `composer`, `go`, `npm`, `yarn`, `pnpm`, `python`, `pipenv`, and `poetry`. @@ -1090,6 +1097,11 @@ For instance if you have a project with an `"examples/"` directory you wish to i Useful to know: Renovate's default ignore is `node_modules` and `bower_components` only, however if you are extending the popular `config:base` preset then it adds ignore patterns for `vendor`, `examples`, `test(s)` and `fixtures` directories too. +## ignorePlugins + +Set this to `true` if running plugins causes problems. +Applicable for Composer only for now. + ## ignorePrAuthor This is usually needed if someone needs to migrate bot accounts, including from hosted app to self-hosted. @@ -1193,7 +1205,16 @@ With the above config, every PR raised by Renovate will have the label `dependen This feature can be used to refresh lock files and keep them up-to-date. "Maintaining" a lock file means recreating it so that every dependency version within it is updated to the latest. -Supported lock files are `package-lock.json`, `yarn.lock`, `composer.lock`, `Gemfile.lock`, `poetry.lock` and `Cargo.lock`. +Supported lock files are: + +- `package-lock.json` +- `yarn.lock` +- `composer.lock` +- `Gemfile.lock` +- `poetry.lock` +- `Cargo.lock` +- `jsonnetfile.lock.json` + Others may be added via feature request. This feature is disabled by default. @@ -1234,7 +1255,7 @@ See [Private npm module support](https://docs.renovatebot.com/getting-started/pr ## npmrcMerge This option exists to provide flexibility about whether `npmrc` strings in config should override `.npmrc` files in the repo, or be merged with them. -In some situations you need the ability to force override `.npmrc` contents in a repo (`npmMerge=false`) while in others you might want to simply supplement the settings already in the `.npmrc` (`npmMerge=true`). +In some situations you need the ability to force override `.npmrc` contents in a repo (`npmrcMerge=false`) while in others you might want to simply supplement the settings already in the `.npmrc` (`npmrcMerge=true`). A use case for the latter is if you are a Renovate bot admin and wish to provide a default token for `npmjs.org` without removing any other `.npmrc` settings which individual repositories have configured (such as scopes/registries). If `false` (default), it means that defining `config.npmrc` will result in any `.npmrc` file in the repo being overridden and therefore its values ignored. @@ -1497,7 +1518,18 @@ Use this field to restrict rules to a particular datasource. e.g. ### matchCurrentVersion -`matchCurrentVersion` can be an exact SemVer version or a SemVer range. +`matchCurrentVersion` can be an exact SemVer version or a SemVer range: + +```json +{ + "packageRules": [ + { + "matchCurrentVersion": ">=1.0.0", + "matchPackageNames": ["angular"] + } + ] +} +``` This field also supports Regular Expressions which must begin and end with `/`. For example, the following enforces that only `1.*` versions will be used: @@ -1652,6 +1684,31 @@ For example to apply a special label for Major updates: } ``` +### replacementName + +Use this field to define the name of a replacement package. +Must be used with `replacementVersion` (see example below). +You can suggest a new community package rule by editing [the `replacements.ts` file on the Renovate repository](https://github.com/renovatebot/renovate/blob/main/lib/config/presets/internal/replacements.ts) and opening a pull request. + +### replacementVersion + +Use this field to define the version of a replacement package. +Must be used with `replacementName`. +For example to replace the npm package `jade` with version `2.0.0` of the package `pug`: + +```json +{ + "packageRules": [ + { + "matchDatasources": ["npm"], + "matchPackageNames": ["jade"], + "replacementName": "pug", + "replacementVersion": "2.0.0" + } + ] +} +``` + ## patch Add to this object if you wish to define rules that apply only to patch updates. @@ -1694,6 +1751,8 @@ For example, GitHub might automerge a Renovate branch even if it's behind the ba ## postUpgradeTasks +Note: post-upgrade tasks can only be used on self-hosted Renovate instances. + 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 additional command line tools that would modify existing files or generate new files when a dependency changes. @@ -1716,16 +1775,17 @@ The `postUpgradeTasks` configuration consists of three fields: ### commands -A list of commands that are executed after Renovate has updated a dependency but before the commit it made +A list of commands that are executed after Renovate has updated a dependency but before the commit is made. ### fileFilters -A list of glob-style matchers that determine which files will be included in the final commit made by Renovate +A list of glob-style matchers that determine which files will be included in the final commit made by Renovate. ### executionMode -Defaults to `update`, but can also be set to `branch`. This sets the level the postUpgradeTask runs on, if set to `update` the postUpgradeTask -will be executed for every dependency on the branch. If set to `branch` the postUpgradeTask is executed for the whole branch. +Defaults to `update`, but can also be set to `branch`. +This sets the level the postUpgradeTask runs on, if set to `update` the postUpgradeTask will be executed for every dependency on the branch. +If set to `branch` the postUpgradeTask is executed for the whole branch. ## prBodyColumns @@ -2218,7 +2278,7 @@ image: my.new.registry/aRepository/andImage:1.21-alpine ## registryUrls Usually Renovate is able to either (a) use the default registries for a datasource, or (b) automatically detect during the manager extract phase which custom registries are in use. -In case there is a need to configure them manually, it can be done using this `registryUrls` field, typically using `packageUrls` like so: +In case there is a need to configure them manually, it can be done using this `registryUrls` field, typically using `packageRules` like so: ```json { @@ -2233,6 +2293,10 @@ In case there is a need to configure them manually, it can be done using this `r The field supports multiple URLs however it is datasource-dependent on whether only the first is used or multiple. +## replacement + +Add to this object if you wish to define rules that apply only to PRs that replace dependencies. + ## respectLatest Similar to `ignoreUnstable`, this option controls whether to update to versions that are greater than the version tagged as `latest` in the repository. @@ -2241,7 +2305,15 @@ By default, `renovate` will update to a version greater than `latest` only if th ## reviewers Must be valid usernames. -If on GitHub and assigning a team to review, use the prefix `team:`, e.g. provide a value like `team:someteam`. + +If on GitHub and assigning a team to review, you must use the prefix `team:` and add the _last part_ of the team name. +Say the full team name on GitHub is `@organization/foo`, then you'd set the config option like this: + +```json +{ + "reviewers": "team:foo" +} +``` ## reviewersFromCodeOwners @@ -2349,7 +2421,7 @@ For example, if you were using Webpack 2.0.0 and versions 2.1.0 and 3.0.0 were b If you were to apply the minor update then Renovate would keep updating the 3.x branch for you as well, e.g. if Webpack 3.0.1 or 3.1.0 were released. If instead you applied the 3.0.0 update then Renovate would clean up the unneeded 2.x branch for you on the next run. -It is recommended that you leave this setting to `true`, because of the polite way that Renovate handles this. +It is recommended that you leave this option to `true`, because of the polite way that Renovate handles this. For example, let's say in the above example that you decided you wouldn't update to Webpack 3 for a long time and don't want to build/test every time a new 3.x version arrives. In that case, simply close the "Update Webpack to version 3.x" PR and it _won't_ be recreated again even if subsequent Webpack 3.x versions are released. You can continue with Webpack 2.x for as long as you want and receive any updates/patches that are made for it. @@ -2357,6 +2429,10 @@ Then eventually when you do want to update to Webpack 3.x you can make that upda After that, Renovate will resume providing you updates to 3.x again! i.e. if you close a major upgrade PR then it won't come back again, but once you make the major upgrade yourself then Renovate will resume providing you with minor or patch updates. +This option also has priority over package groups configured by `packageRule`. +So Renovate will propose separate PRs for major and minor updates of packages even if they are grouped. +If you want to enforce grouped package updates, you need to set this option to `false` within the `packageRule`. + ## separateMinorPatch By default, Renovate won't distinguish between "patch" (e.g. 1.0.x) and "minor" (e.g. 1.x.0) releases - it groups them together. diff --git a/docs/usage/docker.md b/docs/usage/docker.md index a726f1bdac89fd..7185abfdd9521c 100644 --- a/docs/usage/docker.md +++ b/docs/usage/docker.md @@ -126,11 +126,13 @@ If you wish to override Docker settings for one particular type of manager, use For example, to disable digest updates for Docker Compose only but leave them for other managers like `Dockerfile`, you would use this: ```json +{ "docker-compose": { "digest": { "enabled": false } } +} ``` The following configuration options are applicable to Docker: @@ -227,7 +229,7 @@ To get access to the token a custom Renovate Docker image is needed that include The Dockerfile to create such an image can look like this: ```Dockerfile -FROM renovate/renovate:28.20.0 +FROM renovate/renovate:29.32.4 # Include the "Docker tip" which you can find here https://cloud.google.com/sdk/docs/install # under "Installation" for "Debian/Ubuntu" RUN ... diff --git a/docs/usage/examples/self-hosting.md b/docs/usage/examples/self-hosting.md index 97dc3f7ef80421..d3b37607cf0858 100644 --- a/docs/usage/examples/self-hosting.md +++ b/docs/usage/examples/self-hosting.md @@ -15,7 +15,7 @@ If you want to use these package managers to update your lockfiles, you must ens npm install -g yarn pnpm ``` -The same goes for any other third party binary tool like `gradle` or `poetry` - you need to make sure they are installed and the appropriate version before running Renovate. +The same goes for any other third-party binary tool like `gradle` or `poetry` - you need to make sure it is installed and the appropriate version before running Renovate. ### Docker diff --git a/docs/usage/faq.md b/docs/usage/faq.md index d40443ca7bf359..ce87bef8e1c2c8 100644 --- a/docs/usage/faq.md +++ b/docs/usage/faq.md @@ -18,19 +18,6 @@ Renovate will: - Update `yarn.lock` and/or `package-lock.json` files if found - Create Pull Requests immediately after branch creation -## What is this `main` branch I see in the documentation? - -When you create a new repository with Git, Git creates a base branch for you. -The default branch name that Git uses is `master` (this will be changed to `main` later). - -The Git-hosting ecosystem has settled on using `main` to replace `master`. -When you create a new repository on say GitHub or GitLab, you'll get a `main` branch as your base branch. - -It therefore makes sense for Renovate to replace `master` with `main` where possible as well. - -A branch name has no special meaning within the Git program, it's just a name. -The base branch could be called `trunk` or `mainline` or `prod`, and Git would work just as well. - ## Which Renovate versions are officially supported? The Renovate maintainers only support the latest version of Renovate. @@ -56,6 +43,19 @@ Some major platform features are not supported at all by Renovate. | Merge trains | GitLab | [#5573](https://github.com/renovatebot/renovate/issues/5573) | | Configurable merge strategy and message | Only BitBucket for now | [#10867](https://github.com/renovatebot/renovate/issues/10867) [#10868](https://github.com/renovatebot/renovate/issues/10868) [#10869](https://github.com/renovatebot/renovate/issues/10869) [#10870](https://github.com/renovatebot/renovate/issues/10870) | +## What is this `main` branch I see in the documentation? + +When you create a new repository with Git, Git creates a base branch for you. +The default branch name that Git uses is `master` (this will be changed to `main` later). + +The Git-hosting ecosystem has settled on using `main` to replace `master`. +When you create a new repository on say GitHub or GitLab, you'll get a `main` branch as your base branch. + +It therefore makes sense for Renovate to replace `master` with `main` where possible as well. + +A branch name has no special meaning within the Git program, it's just a name. +The base branch could be called `trunk` or `mainline` or `prod`, and Git would work just as well. + ## What if I need to .. ? ### Troubleshoot Renovate @@ -180,12 +180,14 @@ Set the configuration option `labels` to an array of labels to use. e.g. ```json -"packageRules": [ - { - "matchPackageNames": ["abc"], - "assignees": ["importantreviewer"] - } -] +{ + "packageRules": [ + { + "matchPackageNames": ["abc"], + "assignees": ["importantreviewer"] + } + ] +} ``` ### Apply a rule, but only for packages starting with `abc` @@ -193,12 +195,14 @@ e.g. Do the same as above, but instead of using `matchPackageNames`, use `matchPackagePatterns` and a regex: ```json -"packageRules": [ - { - "matchPackagePatterns": "^abc", - "assignees": ["importantreviewer"] - } -] +{ + "packageRules": [ + { + "matchPackagePatterns": "^abc", + "assignees": ["importantreviewer"] + } + ] +} ``` ### Group all packages starting with `abc` together in one PR @@ -206,12 +210,14 @@ Do the same as above, but instead of using `matchPackageNames`, use `matchPackag As above, but apply a `groupName`: ```json -"packageRules": [ - { - "matchPackagePatterns": "^abc", - "groupName": ["abc packages"] - } -] +{ + "packageRules": [ + { + "matchPackagePatterns": "^abc", + "groupName": ["abc packages"] + } + ] +} ``` ### Change the default values for branch name, commit message, PR title or PR description diff --git a/docs/usage/getting-started/running.md b/docs/usage/getting-started/running.md index eb7da8a38a7c1a..a19079c12ef5f8 100644 --- a/docs/usage/getting-started/running.md +++ b/docs/usage/getting-started/running.md @@ -5,7 +5,7 @@ As a Renovate end user, there are two main categories of use: - You self-host Renovate, e.g. by running the pre-built Docker image, or - Someone else is hosting Renovate, and you install/configure it for the repositories you choose -If someone else is hosting Renovate for you, or you are using the WhiteSource Renovate App on GitHub, then you can skip ahead to the Installing Renovate into Repositories section. +If someone else is hosting Renovate for you, or you are using the WhiteSource Renovate App on GitHub, then you can skip ahead to the [installing & onboarding](./installing-onboarding.md) page. ## Self-Hosting Renovate @@ -22,8 +22,8 @@ Self-hosting Renovate means that you are the "administrator" of the bot, which e Renovate's Open Source CLI is built and distributed as the npm package `renovate`. You can run this directly in any Node.js environment - even via `npx` - and it will process all the repositories it is configured with, before exiting. -When you install Renovate from npm it naturally does not come bundled with any third party tools or languages such as Ruby, Python, Composer, Bundler, Poetry, etc. -Therefore if you need Renovate to support any non-npm lock files like Bundler then you'll need to make sure all required third party tools are pre-installed in the same environment alongside Renovate before you run it. +When you install Renovate from npm it naturally does not come bundled with any third-party tools or languages such as Ruby, Python, Composer, Bundler, Poetry, etc. +Therefore if you need Renovate to support any non-npm lock files like Bundler then you'll need to make sure all required third-party tools are pre-installed in the same environment alongside Renovate before you run it. The `renovate` npm package is compatible with all of Renovate's supported platforms. diff --git a/docs/usage/golang.md b/docs/usage/golang.md index 933956a60574e4..67f9b8c5dac9a8 100644 --- a/docs/usage/golang.md +++ b/docs/usage/golang.md @@ -46,9 +46,11 @@ You can force Renovate to use a specific version of Go by setting a constraint. As an example, say you want Renovate to use the latest patch version of the `1.16` Go binary, you'd put this in your Renovate config: ```json +{ "constraints": { "go": "1.16" } +} ``` We do not support patch level versions for the minimum `go` version. diff --git a/docs/usage/key-concepts/dashboard.md b/docs/usage/key-concepts/dashboard.md index c9c2b1252008b0..b7ffb534c8e11e 100644 --- a/docs/usage/key-concepts/dashboard.md +++ b/docs/usage/key-concepts/dashboard.md @@ -29,7 +29,7 @@ To turn on the Dashboard manually, add the `:dependencyDashboard` preset to your Or set `dependencyDashboard` to `true`: -``` +```json { "dependencyDashboard": true } diff --git a/docs/usage/known-limitations.md b/docs/usage/known-limitations.md index 85186da221a865..d5cbd273481ce2 100644 --- a/docs/usage/known-limitations.md +++ b/docs/usage/known-limitations.md @@ -36,8 +36,8 @@ It also means that Renovate's knowledge about dependencies in the base branch is The limitation to only automerge branches which are up-to-date is a decision due to this example: -- Two dependencies are in use: `a@1.0.0` and `b@1.0.0` -- PRs exist for `a@2.0.0` and `b@2.0.0` and both pass tests -- The PR for `a@2.0.0` is automerged -- The PR for `b@2.0.0` remains open, does not have conflicts, and has all tests passing -- However, `a@2.0.0` and `b@2.0.0` are incompatible so merging the PR without rebasing and retesting it first would result in a broken base branch +- Two dependencies are in use: `alice@1.0.0` and `bob@1.0.0` +- PRs exist for `alice@2.0.0` and `bob@2.0.0` and both pass tests +- The PR for `alice@2.0.0` is automerged +- The PR for `bob@2.0.0` remains open, does not have conflicts, and has all tests passing +- However, `alice@2.0.0` and `bob@2.0.0` are incompatible so merging the PR without rebasing and retesting it first would result in a broken base branch diff --git a/docs/usage/modules/manager.md b/docs/usage/modules/manager.md index b53c1b2e762ce4..c91d3df65242f2 100644 --- a/docs/usage/modules/manager.md +++ b/docs/usage/modules/manager.md @@ -3,7 +3,7 @@ Renovate is based around the concept of "package managers", or "managers" for short. These range from traditional package managers like npm, Bundler and Composer through to less traditional concepts like CircleCI or Travis config files. -The goal of Renovate is to detect and maintain all third party dependencies in your repositories, through the use of managers. +The goal of Renovate is to detect and maintain all third-party dependencies in your repositories, through the use of managers. ## Supported Managers diff --git a/docs/usage/node.md b/docs/usage/node.md index 338b37a485a508..b7539087b6e154 100644 --- a/docs/usage/node.md +++ b/docs/usage/node.md @@ -24,12 +24,12 @@ When `binarySource=docker`, such as in the hosted WhiteSource Renovate App, Reno To control which version or constraint is installed, you should use the `engines.npm` property in your `package.json` file. Renovate bot will then use that version constraint for npm when it creates a pull request. -For example, if you want to use at least npm `6.14.11` and also allow newer versions of npm in the `6.x` range, you would put this in your `package.json` file: +For example, if you want to use at least npm `8.1.0` and also allow newer versions of npm in the `8.x` range, you would put this in your `package.json` file: ```json { "engines": { - "npm": "^6.14.11" + "npm": "^8.1.0" } } ``` diff --git a/docs/usage/noise-reduction.md b/docs/usage/noise-reduction.md index a44270a4c34707..ec2cf2a67007fd 100644 --- a/docs/usage/noise-reduction.md +++ b/docs/usage/noise-reduction.md @@ -28,12 +28,14 @@ You may wish to take this further, for example you might want to group together In that case you might create a config like this: ```json +{ "packageRules": [ { - "matchPackagePatterns": [ "eslint" ], + "matchPackagePatterns": ["eslint"], "groupName": "eslint" } ] +} ``` By setting `matchPackagePatterns` to "eslint", it means that any package with ESLint anywhere in its name will be grouped into a `renovate/eslint` branch and related PR. @@ -79,25 +81,29 @@ If you think about it, updates to `eslint` rules don't exactly need to be applie You don't want to get too far behind, so how about we update `eslint` packages only once a month? ```json +{ "packageRules": [ { - "matchPackagePatterns": [ "eslint" ], + "matchPackagePatterns": ["eslint"], "groupName": "eslint", "schedule": ["on the first day of the month"] } ] +} ``` Or perhaps at least weekly: ```json +{ "packageRules": [ { - "matchPackagePatterns": [ "eslint" ], + "matchPackagePatterns": ["eslint"], "groupName": "eslint", "schedule": ["before 2am on monday"] } ] +} ``` If you're wondering what is supported and not, under the hood, the schedule is parsed using [@breejs/later](https://github.com/breejs/later) using the `later.parse.text(scheduleString)` API. @@ -149,15 +155,17 @@ Remember our running `eslint` example? Let's automerge it if all the linting updates pass: ```json +{ "packageRules": [ { - "matchPackagePatterns": [ "eslint" ], + "matchPackagePatterns": ["eslint"], "groupName": "eslint", "schedule": ["before 2am on monday"], "automerge": true, "automergeType": "branch" } ] +} ``` Have you come up with a rule that you think others would benefit from? diff --git a/docs/usage/nuget.md b/docs/usage/nuget.md index 6d5d7af43605cc..a1f718290937ef 100644 --- a/docs/usage/nuget.md +++ b/docs/usage/nuget.md @@ -9,33 +9,39 @@ Renovate supports upgrading dependencies in `.csproj`, `.fsproj`, and `.vbproj` ## Version Support -Only SDK-style `.csproj`/`.fsproj`/`.vbproj`files are currently supported. By default, this includes: +Only SDK-style `.csproj`/`.fsproj`/`.vbproj` files are currently supported. +By default, this includes: - .NET Core 1.0 and above - .NET Standard class libraries - Any `.csproj`/`.fsproj`/`.vbproj` in the SDK-style syntax -To convert your .NET Framework `.csproj`/`.fsproj`/`.vbproj` into an SDK-style project, one can follow the [following guide](https://natemcmaster.com/blog/2017/03/09/vs2015-to-vs2017-upgrade/). +To convert your .NET Framework `.csproj`/`.fsproj`/`.vbproj` into an SDK-style project, follow the steps in this [guide](https://natemcmaster.com/blog/2017/03/09/vs2015-to-vs2017-upgrade/). -## How It Works +## How it works 1. Renovate searches in each repository for any files with a `.csproj`, `.fsproj`, or `.vbproj` extension 1. Existing dependencies are extracted from `` and `` tags 1. Renovate looks up the latest version on [nuget.org](https://nuget.org) (or on [alternate feeds](#Alternate%20feeds)) to determine if any upgrades are available -1. If the source package includes a GitHub URL as its source, and has either a "changelog" file or uses GitHub releases, then Release Notes for each version are embedded in the generated PR +1. If the source package includes a GitHub URL as its source, and has either a "changelog" file or uses GitHub releases, then release notes for each version are embedded in the generated PR + +If your project file references a `packages.config` file, no dependencies will be extracted. +Find out here how to [migrate from `packages.config` to `PackageReference`](https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference). ## Alternate feeds -Renovate by default performs all lookups on `https://api.nuget.org/v3/index.json`, but it also supports alternative NuGet feeds. +By default Renovate performs all lookups on `https://api.nuget.org/v3/index.json`, but you can configure alternative NuGet feeds. Alternative feeds can be specified either [in a `NuGet.config` file](https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#package-source-sections) within your repository (Renovate will not search outside the repository) or in Renovate configuration options: ```json -"nuget": { - "registryUrls": [ - "https://api.nuget.org/v3/index.json", - "https://example1.com/nuget/", - "https://example2.com/nuget/v3/index.json" - ] +{ + "nuget": { + "registryUrls": [ + "https://api.nuget.org/v3/index.json", + "https://example1.com/nuget/", + "https://example2.com/nuget/v3/index.json" + ] + } } ``` @@ -50,10 +56,10 @@ Renovate as a NuGet client supports both versions and will use `v2` unless the c If you have a `v3` feed that does not match this pattern (e.g. JFrog Artifactory) you need to help Renovate by appending `#protocolVersion=3` to the registry URL: ```json -"nuget": { - "registryUrls": [ - "http://myV3feed#protocolVersion=3" - ] +{ + "nuget": { + "registryUrls": ["http://myV3feed#protocolVersion=3"] + } } ``` @@ -62,17 +68,19 @@ If you have a `v3` feed that does not match this pattern (e.g. JFrog Artifactory Credentials for authenticated/private feeds can be provided via host rules in the configuration options (file or command line parameter). ```json -"hostRules": [ - { - "hostType": "nuget", - "matchHost": "http://example1.com/nuget", - "username": "root", - "password": "p4$$w0rd" - } -] +{ + "hostRules": [ + { + "hostType": "nuget", + "matchHost": "http://example1.com/nuget", + "username": "root", + "password": "p4$$w0rd" + } + ] +} ``` -Please note that at the moment only Basic HTTP authentication (via username and password) is supported. +At the moment only Basic HTTP authentication (via username and password) is supported. ## Future work diff --git a/docs/usage/python.md b/docs/usage/python.md index b753e4e990b02b..96cf715ed6a18b 100644 --- a/docs/usage/python.md +++ b/docs/usage/python.md @@ -32,9 +32,11 @@ If you have a specific file or file pattern you want the Renovate bot to find, u e.g.: ```json +{ "pip_requirements": { - "fileMatch": ["my/specifically-named.file", "\.requirements$"] + "fileMatch": ["my/specifically-named.file", "\\.requirements$"] } +} ``` ## Alternate registries @@ -63,9 +65,11 @@ You can use the `registryUrls` array to configure alternate index URL(s). e.g.: ```json +{ "python": { "registryUrls": ["http://example.com/private-pypi/"] } +} ``` Note: the index-url found in the `requirements.txt` file takes precedence over a `registryUrl` configured like the above. @@ -76,14 +80,18 @@ To override the URL found in `requirements.txt`, you need to configure it in `pa The most direct way to disable all Python support in Renovate is like this: ```json +{ "python": { "enabled": false } +} ``` Alternatively, maybe you only want one package manager, such as `npm`. In that case this would enable _only_ `npm`: ```json +{ "enabledManagers": ["npm"] +} ``` diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index 8a90bff42655e6..294dd221287cb0 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -11,6 +11,8 @@ Please also see [Self-Hosted Experimental Options](./self-hosted-experimental.md ## allowCustomCrateRegistries +## allowPlugins + ## allowPostUpgradeCommandTemplating Set to true to allow templating of dependency level post-upgrade commands. @@ -102,10 +104,10 @@ e.g. ## binarySource -Renovate often needs to use third party binaries in its PRs, e.g. `npm` to update `package-lock.json` or `go` to update `go.sum`. +Renovate often needs to use third-party binaries in its PRs, e.g. `npm` to update `package-lock.json` or `go` to update `go.sum`. By default, Renovate will use a child process to run such tools, so they need to be pre-installed before running Renovate and available in the path. -As an alternative, Renovate can use "sidecar" containers for third party tools. +As an alternative, Renovate can use "sidecar" containers for third-party tools. If configured, Renovate will use `docker run` to create containers such as Node.js or Python to run tools within as-needed. For this to work, `docker` needs to be installed and the Docker socket available to Renovate. @@ -248,6 +250,11 @@ e.g. ## endpoint +## executionTimeout + +Default execution timeout in minutes for child processes Renovate creates. +If this option is not set, Renovate will fallback to 15 minutes. + ## exposeAllEnv By default, Renovate only passes a limited set of environment variables to package managers. diff --git a/docs/usage/templates.md b/docs/usage/templates.md index 6bdf0688c235b4..470c1d0efda945 100644 --- a/docs/usage/templates.md +++ b/docs/usage/templates.md @@ -20,3 +20,13 @@ Some are configuration options passed through, while others are generated as par ## Other available fields + +## Additional Handlebars helpers + +### stringToPrettyJSON + +If you want to print pretty JSON with Handlebars you can use the built-in function `stringToPrettyJSON` like this: + +`{{{stringToPrettyJSON myvar}}}` + +In the example above `myvar` is a variable/field, that contains valid JSON. diff --git a/jest.config.ts b/jest.config.ts index 5c94e02bd80b56..6f09d687b37ad1 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -34,8 +34,10 @@ const config: InitialOptionsTsJest = { snapshotSerializers: ['/test/newline-snapshot-serializer.ts'], testEnvironment: 'node', testRunner: 'jest-circus/runner', + watchPathIgnorePatterns: ['/.cache/', '/coverage/'], globals: { 'ts-jest': { + tsconfig: '/tsconfig.spec.json', diagnostics: false, isolatedModules: true, }, diff --git a/lib/config-validator.ts b/lib/config-validator.ts index bd659398db5ca9..aac81196482a76 100644 --- a/lib/config-validator.ts +++ b/lib/config-validator.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node // istanbul ignore file import { dequal } from 'dequal'; -import { readFileSync } from 'fs-extra'; +import { readFile } from 'fs-extra'; import JSON5 from 'json5'; import { configFileNames } from './config/app-strings'; import { massageConfig } from './config/massage'; @@ -11,10 +11,10 @@ import { validateConfig } from './config/validation'; import { logger } from './logger'; import { getConfig as getFileConfig } from './workers/global/config/parse/file'; -/* eslint-disable no-console */ - let returnVal = 0; +/* eslint-disable no-console */ + async function validate( desc: string, config: RenovateConfig, @@ -52,7 +52,7 @@ type PackageJson = { (name) => name !== 'package.json' )) { try { - const rawContent = readFileSync(file, 'utf8'); + const rawContent = await readFile(file, 'utf8'); logger.info(`Validating ${file}`); try { let jsonContent: RenovateConfig; @@ -72,7 +72,7 @@ type PackageJson = { } try { const pkgJson = JSON.parse( - readFileSync('package.json', 'utf8') + await readFile('package.json', 'utf8') ) as PackageJson; if (pkgJson.renovate) { logger.info(`Validating package.json > renovate`); @@ -88,7 +88,7 @@ type PackageJson = { // ignore } try { - const fileConfig = getFileConfig(process.env); + const fileConfig = await getFileConfig(process.env); if (!dequal(fileConfig, {})) { const file = process.env.RENOVATE_CONFIG_FILE ?? 'config.js'; logger.info(`Validating ${file}`); diff --git a/lib/config/decrypt.spec.ts b/lib/config/decrypt.spec.ts index ba2ad76e2d1ff6..3a51e8dcc2b2a9 100644 --- a/lib/config/decrypt.spec.ts +++ b/lib/config/decrypt.spec.ts @@ -1,6 +1,6 @@ import { loadFixture } from '../../test/util'; import { decryptConfig } from './decrypt'; -import { setGlobalConfig } from './global'; +import { GlobalConfig } from './global'; import type { RenovateConfig } from './types'; const privateKey = loadFixture('private.pem', '.'); @@ -12,7 +12,7 @@ describe('config/decrypt', () => { let config: RenovateConfig; beforeEach(() => { config = {}; - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns empty with no privateKey', async () => { delete config.encrypted; @@ -22,51 +22,54 @@ describe('config/decrypt', () => { it('warns if no privateKey found', async () => { config.encrypted = { a: '1' }; const res = await decryptConfig(config, repository); - expect(res.encrypted).not.toBeDefined(); - expect(res.a).not.toBeDefined(); + expect(res.encrypted).toBeUndefined(); + expect(res.a).toBeUndefined(); }); it('handles invalid encrypted type', async () => { config.encrypted = 1; - setGlobalConfig({ privateKey }); + GlobalConfig.set({ privateKey }); const res = await decryptConfig(config, repository); - expect(res.encrypted).not.toBeDefined(); + expect(res.encrypted).toBeUndefined(); }); it('handles invalid encrypted value', async () => { config.encrypted = { a: 1 }; - setGlobalConfig({ privateKey, privateKeyOld: 'invalid-key' }); + GlobalConfig.set({ privateKey, privateKeyOld: 'invalid-key' }); await expect(decryptConfig(config, repository)).rejects.toThrow( 'config-validation' ); }); it('replaces npm token placeholder in npmrc', async () => { - setGlobalConfig({ privateKey: 'invalid-key', privateKeyOld: privateKey }); // test old key failover + GlobalConfig.set({ + privateKey: 'invalid-key', + privateKeyOld: privateKey, + }); // test old key failover config.npmrc = - '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n'; // eslint-disable-line no-template-curly-in-string + '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n'; config.encrypted = { npmToken: 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', }; const res = await decryptConfig(config, repository); - expect(res.encrypted).not.toBeDefined(); - expect(res.npmToken).not.toBeDefined(); - expect(res.npmrc).toEqual( + expect(res.encrypted).toBeUndefined(); + expect(res.npmToken).toBeUndefined(); + expect(res.npmrc).toBe( '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n' ); }); it('appends npm token in npmrc', async () => { - setGlobalConfig({ privateKey }); - config.npmrc = 'foo=bar\n'; // eslint-disable-line no-template-curly-in-string + GlobalConfig.set({ privateKey }); + config.npmrc = 'foo=bar\n'; config.encrypted = { npmToken: 'FLA9YHIzpE7YetAg/P0X46npGRCMqn7hgyzwX5ZQ9wYgu9BRRbTiBVsUIFTyM5BuP1Q22slT2GkWvFvum7GU236Y6QiT7Nr8SLvtsJn2XUuq8H7REFKzdy3+wqyyWbCErYTFyY1dcPM7Ht+CaGDWdd8u/FsoX7AdMRs/X1jNUo6iSmlUiyGlYDKF+QMnCJom1VPVgZXWsGKdjI2MLny991QMaiv0VajmFIh4ENv4CtXOl/1twvIl/6XTXAaqpJJKDTPZEuydi+PHDZmal2RAOfrkH4m0UURa7SlfpUlIg+EaqbNGp85hCYXLwRcEET1OnYr3rH1oYkcYJ40any1tvQ==', }; const res = await decryptConfig(config, repository); - expect(res.encrypted).not.toBeDefined(); - expect(res.npmToken).not.toBeDefined(); + expect(res.encrypted).toBeUndefined(); + expect(res.npmToken).toBeUndefined(); expect(res.npmrc).toMatchSnapshot(); }); it('decrypts nested', async () => { - setGlobalConfig({ privateKey }); + GlobalConfig.set({ privateKey }); config.packageFiles = [ { packageFile: 'package.json', @@ -82,18 +85,18 @@ describe('config/decrypt', () => { 'backend/package.json', ]; const res = await decryptConfig(config, repository); - expect(res.encrypted).not.toBeDefined(); - expect(res.packageFiles[0].devDependencies.encrypted).not.toBeDefined(); - expect(res.packageFiles[0].devDependencies.branchPrefix).toEqual( + expect(res.encrypted).toBeUndefined(); + expect(res.packageFiles[0].devDependencies.encrypted).toBeUndefined(); + expect(res.packageFiles[0].devDependencies.branchPrefix).toBe( 'abcdef-ghijklm-nopqf-stuvwxyz' ); - expect(res.packageFiles[0].devDependencies.npmToken).not.toBeDefined(); - expect(res.packageFiles[0].devDependencies.npmrc).toEqual( + expect(res.packageFiles[0].devDependencies.npmToken).toBeUndefined(); + expect(res.packageFiles[0].devDependencies.npmrc).toBe( '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n' ); }); it('rejects invalid PGP message', async () => { - setGlobalConfig({ privateKey: privateKeyPgp }); + GlobalConfig.set({ privateKey: privateKeyPgp }); config.encrypted = { token: 'long-but-wrong-wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', @@ -133,27 +136,27 @@ describe('config/decrypt', () => { ); }); it('handles PGP org constraint', async () => { - setGlobalConfig({ privateKey: privateKeyPgp }); + GlobalConfig.set({ privateKey: privateKeyPgp }); config.encrypted = { token: 'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10', }; const res = await decryptConfig(config, repository); - expect(res.encrypted).not.toBeDefined(); - expect(res.token).toEqual('123'); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); await expect(decryptConfig(config, 'wrong/org')).rejects.toThrow( 'config-validation' ); }); it('handles PGP org/repo constraint', async () => { - setGlobalConfig({ privateKey: privateKeyPgp }); + GlobalConfig.set({ privateKey: privateKeyPgp }); config.encrypted = { token: 'wcFMAw+4H7SgaqGOAQ//Wp7N0PaDZp0uOdwsc1CuqAq0UPcq+IQdHyKpJs3tHiCecXBHogy4P+rY9nGaUrVneCr4HexuKGuyJf1yl0ZqFffAUac5PjF8eDvjukQGOUq4aBlOogJCEefnuuVxVJx+NRR5iF1P6v57bmI1c+zoqZI/EQB30KU6O1BsdGPLUA/+R3dwCZd5Mbd36s34eYBasqcY9/QbqFcpElXMEPMse3kMCsVXPbZ+UMjtPJiBPUmtJq+ifnu1LzDrfshusSQMwgd/QNk7nEsijiYKllkWhHTP6g7zigvJ46x0h6AYS108YiuK3B9XUhXN9m05Ac6KTEEUdRI3E/dK2dQuRkLjXC8wceQm4A19Gm0uHoMIJYOCbiVoBCH6ayvKbZWZV5lZ4D1JbDNGmKeIj6OX9XWEMKiwTx0Xe89V7BdJzwIGrL0TCLtXuYWZ/R2k+UuBqtgzr44BsBqMpKUA0pcGBoqsEou1M05Ae9fJMF6ADezF5UQZPxT1hrMldiTp3p9iHGfWN2tKHeoW/8CqlIqg9JEkTc+Pl/L9E6ndy5Zjf097PvcmSGhxUQBE7XlrZoIlGhiEU/1HPMen0UUIs0LUu1ywpjCex2yTWnU2YmEwy0MQI1sekSr96QFxDDz9JcynYOYbqR/X9pdxEWyzQ+NJ3n6K97nE1Dj9Sgwu7mFGiUdNkf/SUAF0eZi/eXg71qumpMGBd4eWPtgkeMPLHjvMSYw9vBUfcoKFz6RJ4woG0dw5HOFkPnIjXKWllnl/o01EoBp/o8uswsIS9Nb8i+bp27U6tAHE', }; const res = await decryptConfig(config, repository); - expect(res.encrypted).not.toBeDefined(); - expect(res.token).toEqual('123'); + expect(res.encrypted).toBeUndefined(); + expect(res.token).toBe('123'); await expect(decryptConfig(config, 'abc/defg')).rejects.toThrow( 'config-validation' ); diff --git a/lib/config/decrypt.ts b/lib/config/decrypt.ts index 86a51a435b78c7..3d06a171f28a8a 100644 --- a/lib/config/decrypt.ts +++ b/lib/config/decrypt.ts @@ -5,7 +5,7 @@ import { logger } from '../logger'; import { maskToken } from '../util/mask'; import { regEx } from '../util/regex'; import { add } from '../util/sanitize'; -import { getGlobalConfig } from './global'; +import { GlobalConfig } from './global'; import type { RenovateConfig } from './types'; export async function tryDecryptPgp( @@ -99,7 +99,7 @@ export async function tryDecrypt( const orgName = org.replace(regEx(/\/$/), ''); // Strip trailing slash if (is.nonEmptyString(repo)) { const scopedRepository = `${orgName}/${repo}`; - if (scopedRepository === repository) { + if (scopedRepository.toLowerCase() === repository.toLowerCase()) { decryptedStr = value; } else { logger.debug( @@ -107,12 +107,14 @@ export async function tryDecrypt( 'Secret is scoped to a different repository' ); const error = new Error('config-validation'); - error.validationError = `Encrypted secret is scoped to a different repository: ${scopedRepository}.`; + error.validationError = `Encrypted secret is scoped to a different repository: "${scopedRepository}".`; throw error; } } else { const scopedOrg = `${orgName}/`; - if (repository.startsWith(scopedOrg)) { + if ( + repository.toLowerCase().startsWith(scopedOrg.toLowerCase()) + ) { decryptedStr = value; } else { logger.debug( @@ -120,7 +122,7 @@ export async function tryDecrypt( 'Secret is scoped to a different org' ); const error = new Error('config-validation'); - error.validationError = `Encrypted secret is scoped to a different org" ${scopedOrg}.`; + error.validationError = `Encrypted secret is scoped to a different org: "${scopedOrg}".`; throw error; } } @@ -153,7 +155,7 @@ export async function decryptConfig( ): Promise { logger.trace({ config }, 'decryptConfig()'); const decryptedConfig = { ...config }; - const { privateKey, privateKeyOld } = getGlobalConfig(); + const { privateKey, privateKeyOld } = GlobalConfig.get(); for (const [key, val] of Object.entries(config)) { if (key === 'encrypted' && is.object(val)) { logger.debug({ config: val }, 'Found encrypted config'); diff --git a/lib/config/global.ts b/lib/config/global.ts index 0d17dd3992d23e..4c81facf974119 100644 --- a/lib/config/global.ts +++ b/lib/config/global.ts @@ -1,39 +1,53 @@ import type { RenovateConfig, RepoGlobalConfig } from './types'; -let repoGlobalConfig: RepoGlobalConfig = {}; +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)[] = [ + 'allowCustomCrateRegistries', + 'allowedPostUpgradeCommands', + 'allowPlugins', + 'allowPostUpgradeCommandTemplating', + 'allowScripts', + 'binarySource', + 'cacheDir', + 'customEnvVariables', + 'dockerChildPrefix', + 'dockerImagePrefix', + 'dockerUser', + 'dryRun', + 'exposeAllEnv', + 'executionTimeout', + 'localDir', + 'migratePresets', + 'privateKey', + 'privateKeyOld', + ]; -// TODO: once global config work is complete, add a test to make sure this list includes all options with globalOnly=true (#9603) -const repoGlobalOptions = [ - 'allowCustomCrateRegistries', - 'allowPostUpgradeCommandTemplating', - 'allowScripts', - 'allowedPostUpgradeCommands', - 'binarySource', - 'customEnvVariables', - 'dockerChildPrefix', - 'dockerImagePrefix', - 'dockerUser', - 'dryRun', - 'exposeAllEnv', - 'migratePresets', - 'privateKey', - 'privateKeyOld', - 'localDir', - 'cacheDir', -]; + private static config: RepoGlobalConfig = {}; -export function setGlobalConfig( - config: RenovateConfig | RepoGlobalConfig = {} -): RenovateConfig { - repoGlobalConfig = {}; - const result = { ...config }; - for (const option of repoGlobalOptions) { - repoGlobalConfig[option] = config[option]; - delete result[option]; // eslint-disable-line no-param-reassign + static get(): RepoGlobalConfig; + static get( + key?: Key + ): RepoGlobalConfig[Key]; + static get( + key?: Key + ): RepoGlobalConfig | RepoGlobalConfig[Key] { + return key ? GlobalConfig.config[key] : GlobalConfig.config; } - return result; -} -export function getGlobalConfig(): RepoGlobalConfig { - return repoGlobalConfig; + static set(config: RenovateConfig | RepoGlobalConfig): RenovateConfig { + GlobalConfig.reset(); + + const result = { ...config }; + for (const option of GlobalConfig.OPTIONS) { + GlobalConfig.config[option] = config[option] as never; + delete result[option]; + } + + return result; + } + + static reset(): void { + GlobalConfig.config = {}; + } } diff --git a/lib/config/index.spec.ts b/lib/config/index.spec.ts index f0ab843c78e13c..8aff5b04ab866e 100644 --- a/lib/config/index.spec.ts +++ b/lib/config/index.spec.ts @@ -22,8 +22,8 @@ describe('config/index', () => { }; const configParser = await import('./index'); const config = configParser.mergeChildConfig(parentConfig, childConfig); - expect(config.foo).toEqual('bar'); - expect(config.rangeStrategy).toEqual('replace'); + expect(config.foo).toBe('bar'); + expect(config.rangeStrategy).toBe('replace'); expect(config.lockFileMaintenance.schedule).toEqual(['on monday']); expect(config.lockFileMaintenance).toMatchSnapshot(); }); @@ -57,7 +57,7 @@ describe('config/index', () => { const configParser = await import('./index'); const config = configParser.mergeChildConfig(parentConfig, childConfig); expect(config.constraints).toMatchSnapshot(); - expect(config.constraints.node).toEqual('<15'); + expect(config.constraints.node).toBe('<15'); }); it('handles null parent packageRules', async () => { const parentConfig = { ...defaultConfig }; diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts index f0bbd8a9fead6c..8f4a4202ac53d7 100644 --- a/lib/config/migration.spec.ts +++ b/lib/config/migration.spec.ts @@ -1,6 +1,6 @@ import { PlatformId } from '../constants'; import { getConfig } from './defaults'; -import { setGlobalConfig } from './global'; +import { GlobalConfig } from './global'; import * as configMigration from './migration'; import type { MigratedConfig, @@ -164,8 +164,8 @@ describe('config/migration', () => { ); expect(migratedConfig).toMatchSnapshot(); expect(isMigrated).toBeTrue(); - expect(migratedConfig.depTypes).not.toBeDefined(); - expect(migratedConfig.automerge).toEqual(false); + expect(migratedConfig.depTypes).toBeUndefined(); + expect(migratedConfig.automerge).toBe(false); expect(migratedConfig.packageRules).toHaveLength(9); expect(migratedConfig.hostRules).toHaveLength(1); }); @@ -186,16 +186,12 @@ describe('config/migration', () => { expect(migratedConfig).toMatchSnapshot(); expect(isMigrated).toBeTrue(); expect(migratedConfig.major.schedule).toHaveLength(2); - expect(migratedConfig.major.schedule[0]).toEqual('after 10pm'); - expect(migratedConfig.major.schedule[1]).toEqual('before 7am'); + expect(migratedConfig.major.schedule[0]).toBe('after 10pm'); + expect(migratedConfig.major.schedule[1]).toBe('before 7am'); expect(migratedConfig.minor.schedule).toMatchSnapshot(); expect(migratedConfig.minor.schedule).toHaveLength(2); - expect(migratedConfig.minor.schedule[0]).toEqual( - 'after 10pm every weekday' - ); - expect(migratedConfig.minor.schedule[1]).toEqual( - 'before 7am every weekday' - ); + expect(migratedConfig.minor.schedule[0]).toBe('after 10pm every weekday'); + expect(migratedConfig.minor.schedule[1]).toBe('before 7am every weekday'); }); it('migrates every friday', () => { const config = { @@ -207,7 +203,7 @@ describe('config/migration', () => { parentConfig ); expect(isMigrated).toBeTrue(); - expect(migratedConfig.schedule).toEqual('on friday'); + expect(migratedConfig.schedule).toBe('on friday'); }); it('migrates semantic prefix with no scope', () => { const config = { @@ -707,7 +703,7 @@ describe('config/migration', () => { }); }); it('it migrates presets', () => { - setGlobalConfig({ + GlobalConfig.set({ migratePresets: { '@org': 'local>org/renovate-config', '@org2/foo': '', diff --git a/lib/config/migration.ts b/lib/config/migration.ts index d0482ef5aa566b..4219e35676bc47 100644 --- a/lib/config/migration.ts +++ b/lib/config/migration.ts @@ -4,8 +4,8 @@ import { dequal } from 'dequal'; import { logger } from '../logger'; import { clone } from '../util/clone'; import { regEx } from '../util/regex'; -import { getGlobalConfig } from './global'; -import { applyMigrations } from './migrations'; +import { GlobalConfig } from './global'; +import { MigrationsService } from './migrations'; import { getOptions } from './options'; import { removedPresets } from './presets/common'; import type { @@ -20,23 +20,6 @@ const options = getOptions(); let optionTypes: Record; -const removedOptions = [ - 'maintainYarnLock', - 'yarnCacheFolder', - 'yarnMaintenanceBranchName', - 'yarnMaintenanceCommitMessage', - 'yarnMaintenancePrTitle', - 'yarnMaintenancePrBody', - 'groupBranchName', - 'groupBranchName', - 'groupCommitMessage', - 'groupPrTitle', - 'groupPrBody', - 'statusCheckVerify', - 'lazyGrouping', - 'supportPolicy', -]; - // Returns a migrated config export function migrateConfig( config: RenovateConfig, @@ -50,7 +33,8 @@ export function migrateConfig( optionTypes[option.name] = option.type; }); } - const migratedConfig = clone(config) as MigratedRenovateConfig; + const newConfig = MigrationsService.run(config); + const migratedConfig = clone(newConfig) as MigratedRenovateConfig; const depTypes = [ 'dependencies', 'devDependencies', @@ -58,12 +42,9 @@ export function migrateConfig( 'optionalDependencies', 'peerDependencies', ]; - const { migratePresets } = getGlobalConfig(); - applyMigrations(config, migratedConfig); + const { migratePresets } = GlobalConfig.get(); for (const [key, val] of Object.entries(config)) { - if (removedOptions.includes(key)) { - delete migratedConfig[key]; - } else if (key === 'pathRules') { + if (key === 'pathRules') { if (is.array(val)) { migratedConfig.packageRules = is.array(migratedConfig.packageRules) ? migratedConfig.packageRules @@ -85,13 +66,6 @@ export function migrateConfig( migratedConfig[newKey] = true; } delete migratedConfig[key]; - } else if (key === 'gomodTidy') { - if (val) { - migratedConfig.postUpdateOptions = - migratedConfig.postUpdateOptions || []; - migratedConfig.postUpdateOptions.push('gomodTidy'); - } - delete migratedConfig.gomodTidy; } else if (key === 'semanticCommits') { if (val === true) { migratedConfig.semanticCommits = 'enabled'; @@ -211,16 +185,6 @@ export function migrateConfig( if (val === false) { migratedConfig.rebaseWhen = 'never'; } - } else if (key === 'exposeEnv') { - migratedConfig.exposeAllEnv = val; - delete migratedConfig.exposeEnv; - } else if (key === 'trustLevel') { - delete migratedConfig.trustLevel; - if (val === 'high') { - migratedConfig.allowCustomCrateRegistries ??= true; - migratedConfig.allowScripts ??= true; - migratedConfig.exposeAllEnv ??= true; - } } else if (key === 'ignoreNpmrcFile') { delete migratedConfig.ignoreNpmrcFile; if (!is.string(migratedConfig.npmrc)) { @@ -305,9 +269,6 @@ export function migrateConfig( } } delete migratedConfig.unpublishSafe; - } else if (key === 'versionScheme') { - migratedConfig.versioning = val; - delete migratedConfig.versionScheme; } else if ( key === 'automergeType' && is.string(val) && @@ -316,31 +277,22 @@ export function migrateConfig( migratedConfig.automergeType = 'branch'; } else if (key === 'automergeMinor') { migratedConfig.minor = migratedConfig.minor || {}; - migratedConfig.minor.automerge = val == true; // eslint-disable-line eqeqeq + migratedConfig.minor.automerge = !!val; delete migratedConfig[key]; } else if (key === 'automergeMajor') { migratedConfig.major = migratedConfig.major || {}; - migratedConfig.major.automerge = val == true; // eslint-disable-line eqeqeq + migratedConfig.major.automerge = !!val; delete migratedConfig[key]; - } else if (key === 'multipleMajorPrs') { - delete migratedConfig.multipleMajorPrs; - migratedConfig.separateMultipleMajor = val; } else if (key === 'renovateFork' && is.boolean(val)) { delete migratedConfig.renovateFork; migratedConfig.includeForks = val; } else if (key === 'separateMajorReleases') { delete migratedConfig.separateMultipleMajor; migratedConfig.separateMajorMinor = val; - } else if (key === 'separatePatchReleases') { - delete migratedConfig.separatePatchReleases; - migratedConfig.separateMinorPatch = val; } else if (key === 'automergePatch') { migratedConfig.patch = migratedConfig.patch || {}; - migratedConfig.patch.automerge = val == true; // eslint-disable-line eqeqeq + migratedConfig.patch.automerge = !!val; delete migratedConfig[key]; - } else if (key === 'ignoreNodeModules') { - delete migratedConfig.ignoreNodeModules; - migratedConfig.ignorePaths = val ? ['node_modules/'] : []; } else if ( key === 'automerge' && is.string(val) && @@ -374,9 +326,6 @@ export function migrateConfig( migratedConfig.packages ); delete migratedConfig.packages; - } else if (key === 'excludedPackageNames') { - migratedConfig.excludePackageNames = val; - delete migratedConfig.excludedPackageNames; } else if (key === 'packageName') { migratedConfig.packageNames = [val]; delete migratedConfig.packageName; @@ -541,8 +490,6 @@ export function migrateConfig( migratedConfig.suppressNotifications || []; migratedConfig.suppressNotifications.push('deprecationWarningIssues'); } - } else if (key === 'binarySource' && val === 'auto') { - migratedConfig.binarySource = 'global'; } else if (key === 'composerIgnorePlatformReqs') { if (val === true) { migratedConfig.composerIgnorePlatformReqs = []; diff --git a/lib/config/migrations/base/abstract-migration.ts b/lib/config/migrations/base/abstract-migration.ts new file mode 100644 index 00000000000000..56d6fb7548bf22 --- /dev/null +++ b/lib/config/migrations/base/abstract-migration.ts @@ -0,0 +1,26 @@ +import type { RenovateConfig } from '../../types'; +import type { Migration } from '../types'; + +export abstract class AbstractMigration implements Migration { + readonly propertyName: string; + + protected readonly originalConfig: RenovateConfig; + + protected readonly migratedConfig: RenovateConfig; + + constructor( + propertyName: string, + originalConfig: RenovateConfig, + migratedConfig: RenovateConfig + ) { + this.propertyName = propertyName; + this.originalConfig = originalConfig; + this.migratedConfig = migratedConfig; + } + + abstract run(): void; + + protected delete(property: string): void { + delete this.migratedConfig[property]; + } +} diff --git a/lib/config/migrations/base/remove-property-migration.ts b/lib/config/migrations/base/remove-property-migration.ts new file mode 100644 index 00000000000000..0b2ad2496963b2 --- /dev/null +++ b/lib/config/migrations/base/remove-property-migration.ts @@ -0,0 +1,7 @@ +import { AbstractMigration } from './abstract-migration'; + +export class RemovePropertyMigration extends AbstractMigration { + override run(): void { + this.delete(this.propertyName); + } +} diff --git a/lib/config/migrations/base/rename-property-migration.ts b/lib/config/migrations/base/rename-property-migration.ts new file mode 100644 index 00000000000000..c317634be40ea4 --- /dev/null +++ b/lib/config/migrations/base/rename-property-migration.ts @@ -0,0 +1,23 @@ +import type { RenovateConfig } from '../../types'; +import { AbstractMigration } from './abstract-migration'; + +export class RenamePropertyMigration extends AbstractMigration { + protected readonly newPropertyName: string; + + constructor( + deprecatedPropertyName: string, + newPropertyName: string, + originalConfig: RenovateConfig, + migratedConfig: RenovateConfig + ) { + super(deprecatedPropertyName, originalConfig, migratedConfig); + this.newPropertyName = newPropertyName; + } + + override run(): void { + this.delete(this.propertyName); + + this.migratedConfig[this.newPropertyName] = + this.originalConfig[this.propertyName]; + } +} diff --git a/lib/config/migrations/custom/binary-source-migration.spec.ts b/lib/config/migrations/custom/binary-source-migration.spec.ts new file mode 100644 index 00000000000000..22ef956ba40e2b --- /dev/null +++ b/lib/config/migrations/custom/binary-source-migration.spec.ts @@ -0,0 +1,11 @@ +import { MigrationsService } from '../migrations-service'; + +describe('config/migrations/custom/binary-source-migration', () => { + it('should migrate "auto" to "global"', () => { + const migratedConfig = MigrationsService.run({ + binarySource: 'auto', + }); + + expect(migratedConfig.binarySource).toBe('global'); + }); +}); diff --git a/lib/config/migrations/custom/binary-source-migration.ts b/lib/config/migrations/custom/binary-source-migration.ts new file mode 100644 index 00000000000000..1452897133193b --- /dev/null +++ b/lib/config/migrations/custom/binary-source-migration.ts @@ -0,0 +1,14 @@ +import type { RenovateConfig } from '../../types'; +import { AbstractMigration } from '../base/abstract-migration'; + +export class BinarySourceMigration extends AbstractMigration { + constructor(originalConfig: RenovateConfig, migratedConfig: RenovateConfig) { + super('binarySource', originalConfig, migratedConfig); + } + + override run(): void { + if (this.originalConfig.binarySource === 'auto') { + this.migratedConfig.binarySource = 'global'; + } + } +} diff --git a/lib/config/migrations/custom/go-mod-tidy-migration.spec.ts b/lib/config/migrations/custom/go-mod-tidy-migration.spec.ts new file mode 100644 index 00000000000000..f0c7be274fe469 --- /dev/null +++ b/lib/config/migrations/custom/go-mod-tidy-migration.spec.ts @@ -0,0 +1,31 @@ +import { MigrationsService } from '../migrations-service'; + +describe('config/migrations/custom/go-mod-tidy-migration', () => { + it('should add postUpdateOptions option when true', () => { + const migratedConfig = MigrationsService.run({ + gomodTidy: true, + postUpdateOptions: ['test'], + }); + + expect(migratedConfig).not.toHaveProperty('gomodTidy'); + expect(migratedConfig.postUpdateOptions).toEqual(['test', 'gomodTidy']); + }); + + it('should handle case when postUpdateOptions is not defined ', () => { + const migratedConfig = MigrationsService.run({ + gomodTidy: true, + }); + + expect(migratedConfig).not.toHaveProperty('gomodTidy'); + expect(migratedConfig.postUpdateOptions).toEqual(['gomodTidy']); + }); + + it('should only remove when false', () => { + const migratedConfig = MigrationsService.run({ + gomodTidy: false, + }); + + expect(migratedConfig).not.toHaveProperty('gomodTidy'); + expect(migratedConfig).not.toHaveProperty('postUpdateOptions'); + }); +}); diff --git a/lib/config/migrations/custom/go-mod-tidy-migration.ts b/lib/config/migrations/custom/go-mod-tidy-migration.ts new file mode 100644 index 00000000000000..b655489da2116a --- /dev/null +++ b/lib/config/migrations/custom/go-mod-tidy-migration.ts @@ -0,0 +1,19 @@ +import type { RenovateConfig } from '../../types'; +import { AbstractMigration } from '../base/abstract-migration'; + +export class GoModTidyMigration extends AbstractMigration { + constructor(originalConfig: RenovateConfig, migratedConfig: RenovateConfig) { + super('gomodTidy', originalConfig, migratedConfig); + } + + override run(): void { + const { gomodTidy, postUpdateOptions } = this.originalConfig; + + this.delete(this.propertyName); + + if (gomodTidy) { + this.migratedConfig.postUpdateOptions ??= postUpdateOptions ?? []; + this.migratedConfig.postUpdateOptions.push('gomodTidy'); + } + } +} diff --git a/lib/config/migrations/custom/ignore-node-modules-migration.spec.ts b/lib/config/migrations/custom/ignore-node-modules-migration.spec.ts new file mode 100644 index 00000000000000..4f2d4d000a8129 --- /dev/null +++ b/lib/config/migrations/custom/ignore-node-modules-migration.spec.ts @@ -0,0 +1,11 @@ +import { MigrationsService } from '../migrations-service'; + +describe('config/migrations/custom/ignore-node-modules-migration', () => { + it('should migrate to ignorePaths', () => { + const migratedConfig = MigrationsService.run({ + ignoreNodeModules: true, + }); + + expect(migratedConfig.ignorePaths).toEqual(['node_modules/']); + }); +}); diff --git a/lib/config/migrations/custom/ignore-node-modules-migration.ts b/lib/config/migrations/custom/ignore-node-modules-migration.ts new file mode 100644 index 00000000000000..976b53df3cd0e0 --- /dev/null +++ b/lib/config/migrations/custom/ignore-node-modules-migration.ts @@ -0,0 +1,16 @@ +import type { RenovateConfig } from '../../types'; +import { AbstractMigration } from '../base/abstract-migration'; + +export class IgnoreNodeModulesMigration extends AbstractMigration { + constructor(originalConfig: RenovateConfig, migratedConfig: RenovateConfig) { + super('ignoreNodeModules', originalConfig, migratedConfig); + } + + override run(): void { + this.delete(this.propertyName); + + this.migratedConfig.ignorePaths = this.originalConfig.ignoreNodeModules + ? ['node_modules/'] + : []; + } +} diff --git a/lib/config/migrations/custom/required-status-checks-migration.spec.ts b/lib/config/migrations/custom/required-status-checks-migration.spec.ts new file mode 100644 index 00000000000000..2fdd67820869e0 --- /dev/null +++ b/lib/config/migrations/custom/required-status-checks-migration.spec.ts @@ -0,0 +1,12 @@ +import { MigrationsService } from '../migrations-service'; + +describe('config/migrations/custom/required-status-checks-migration', () => { + it('should migrate requiredStatusChecks=null to ignoreTests=true', () => { + const migratedConfig = MigrationsService.run({ + requiredStatusChecks: null, + }); + + expect(migratedConfig).not.toHaveProperty('requiredStatusChecks'); + expect(migratedConfig.ignoreTests).toBeTrue(); + }); +}); diff --git a/lib/config/migrations/custom/required-status-checks-migration.ts b/lib/config/migrations/custom/required-status-checks-migration.ts new file mode 100644 index 00000000000000..cd06869c20e18c --- /dev/null +++ b/lib/config/migrations/custom/required-status-checks-migration.ts @@ -0,0 +1,16 @@ +import type { RenovateConfig } from '../../types'; +import { AbstractMigration } from '../base/abstract-migration'; + +export class RequiredStatusChecksMigration extends AbstractMigration { + constructor(originalConfig: RenovateConfig, migratedConfig: RenovateConfig) { + super('requiredStatusChecks', originalConfig, migratedConfig); + } + + override run(): void { + this.delete(this.propertyName); + + if (this.originalConfig.requiredStatusChecks === null) { + this.migratedConfig.ignoreTests = true; + } + } +} diff --git a/lib/config/migrations/custom/trust-level-migration.spec.ts b/lib/config/migrations/custom/trust-level-migration.spec.ts new file mode 100644 index 00000000000000..5fac9753ea1fba --- /dev/null +++ b/lib/config/migrations/custom/trust-level-migration.spec.ts @@ -0,0 +1,26 @@ +import { MigrationsService } from '../migrations-service'; + +describe('config/migrations/custom/trust-level-migration', () => { + it('should handle hight level', () => { + const migratedConfig = MigrationsService.run({ + trustLevel: 'high', + }); + + expect(migratedConfig.allowCustomCrateRegistries).toBeTrue(); + expect(migratedConfig.allowScripts).toBeTrue(); + expect(migratedConfig.exposeAllEnv).toBeTrue(); + }); + + it('should not rewrite provided properties', () => { + const migratedConfig = MigrationsService.run({ + allowCustomCrateRegistries: false, + allowScripts: false, + exposeAllEnv: false, + trustLevel: 'high', + }); + + expect(migratedConfig.allowCustomCrateRegistries).toBeFalse(); + expect(migratedConfig.allowScripts).toBeFalse(); + expect(migratedConfig.exposeAllEnv).toBeFalse(); + }); +}); diff --git a/lib/config/migrations/custom/trust-level-migration.ts b/lib/config/migrations/custom/trust-level-migration.ts new file mode 100644 index 00000000000000..b9e09077f45739 --- /dev/null +++ b/lib/config/migrations/custom/trust-level-migration.ts @@ -0,0 +1,21 @@ +import type { RenovateConfig } from '../../types'; +import { AbstractMigration } from '../base/abstract-migration'; + +export class TrustLevelMigration extends AbstractMigration { + constructor(originalConfig: RenovateConfig, migratedConfig: RenovateConfig) { + super('trustLevel', originalConfig, migratedConfig); + } + + override run(): void { + this.delete(this.propertyName); + + if (this.originalConfig.trustLevel === 'high') { + this.migratedConfig.allowCustomCrateRegistries = + this.originalConfig.allowCustomCrateRegistries ?? true; + this.migratedConfig.allowScripts = + this.originalConfig.allowScripts ?? true; + this.migratedConfig.exposeAllEnv = + this.originalConfig.exposeAllEnv ?? true; + } + } +} diff --git a/lib/config/migrations/index.ts b/lib/config/migrations/index.ts index 1f6c961c293031..2831c79f22e53b 100644 --- a/lib/config/migrations/index.ts +++ b/lib/config/migrations/index.ts @@ -1,18 +1 @@ -import { RenovateConfig } from '../types'; -import type { Migration } from './migration'; -import { RequiredStatusChecksMigration } from './required-status-checks-migration'; - -export function applyMigrations( - originalConfig: RenovateConfig, - migratedConfig: RenovateConfig -): RenovateConfig { - const migrations: Migration[] = [ - new RequiredStatusChecksMigration(originalConfig, migratedConfig), - ]; - - for (const migration of migrations) { - migration.migrate(); - } - - return migratedConfig; -} +export { MigrationsService } from './migrations-service'; diff --git a/lib/config/migrations/migration.ts b/lib/config/migrations/migration.ts deleted file mode 100644 index 616b96caeaeb17..00000000000000 --- a/lib/config/migrations/migration.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { RenovateConfig } from '../types'; - -export abstract class Migration { - protected readonly originalConfig: RenovateConfig; - - protected readonly migratedConfig: RenovateConfig; - - constructor(originalConfig: RenovateConfig, migratedConfig: RenovateConfig) { - this.originalConfig = originalConfig; - this.migratedConfig = migratedConfig; - } - - abstract migrate(): void; - - protected delete(property: string): void { - delete this.migratedConfig[property]; - } -} diff --git a/lib/config/migrations/migrations-service.spec.ts b/lib/config/migrations/migrations-service.spec.ts new file mode 100644 index 00000000000000..222da9a9ca05ae --- /dev/null +++ b/lib/config/migrations/migrations-service.spec.ts @@ -0,0 +1,45 @@ +import type { RenovateConfig } from '../types'; +import { MigrationsService } from './migrations-service'; + +describe('config/migrations/migrations-service', () => { + it('should remove deprecated properties', () => { + for (const property of MigrationsService.removedProperties) { + const originalConfig: RenovateConfig = { + [property]: 'test', + }; + + const migratedConfig = MigrationsService.run(originalConfig); + expect(migratedConfig).not.toHaveProperty(property); + } + }); + + it('should rename renamed properties', () => { + for (const [ + oldPropertyName, + newPropertyName, + ] of MigrationsService.renamedProperties.entries()) { + const originalConfig: RenovateConfig = { + [oldPropertyName]: 'test', + }; + + const migratedConfig = MigrationsService.run(originalConfig); + expect(migratedConfig).not.toHaveProperty(oldPropertyName); + expect(migratedConfig[newPropertyName]).toBe('test'); + } + }); + + it('should save original order of properties', () => { + const originalConfig: RenovateConfig = { + exposeEnv: true, + versionScheme: 'test', + excludedPackageNames: ['test'], + }; + const migratedConfig = MigrationsService.run(originalConfig); + + const mappedProperties = Object.keys(originalConfig).map((property) => + MigrationsService.renamedProperties.get(property) + ); + + expect(mappedProperties).toEqual(Object.keys(migratedConfig)); + }); +}); diff --git a/lib/config/migrations/migrations-service.ts b/lib/config/migrations/migrations-service.ts new file mode 100644 index 00000000000000..d1cee89acb423a --- /dev/null +++ b/lib/config/migrations/migrations-service.ts @@ -0,0 +1,95 @@ +import type { RenovateConfig } from '../types'; +import { RemovePropertyMigration } from './base/remove-property-migration'; +import { RenamePropertyMigration } from './base/rename-property-migration'; +import { BinarySourceMigration } from './custom/binary-source-migration'; +import { GoModTidyMigration } from './custom/go-mod-tidy-migration'; +import { IgnoreNodeModulesMigration } from './custom/ignore-node-modules-migration'; +import { RequiredStatusChecksMigration } from './custom/required-status-checks-migration'; +import { TrustLevelMigration } from './custom/trust-level-migration'; +import type { Migration } from './types'; + +export class MigrationsService { + static readonly removedProperties: ReadonlySet = new Set([ + 'gitFs', + 'groupBranchName', + 'groupCommitMessage', + 'groupPrBody', + 'groupPrTitle', + 'lazyGrouping', + 'maintainYarnLock', + 'statusCheckVerify', + 'supportPolicy', + 'yarnCacheFolder', + 'yarnMaintenanceBranchName', + 'yarnMaintenanceCommitMessage', + 'yarnMaintenancePrBody', + 'yarnMaintenancePrTitle', + ]); + + static readonly renamedProperties: ReadonlyMap = new Map([ + ['exposeEnv', 'exposeAllEnv'], + ['separatePatchReleases', 'separateMinorPatch'], + ['multipleMajorPrs', 'separateMultipleMajor'], + ['excludedPackageNames', 'excludePackageNames'], + ['versionScheme', 'versioning'], + ]); + + static run(originalConfig: RenovateConfig): RenovateConfig { + const migratedConfig: RenovateConfig = {}; + const migrations = MigrationsService.getMigrations( + originalConfig, + migratedConfig + ); + + for (const [key, value] of Object.entries(originalConfig)) { + migratedConfig[key] ??= value; + const migration = migrations.find((item) => item.propertyName === key); + migration?.run(); + } + + return migratedConfig; + } + + private static getMigrations( + originalConfig: RenovateConfig, + migratedConfig: RenovateConfig + ): Migration[] { + const migrations: Migration[] = []; + + for (const propertyName of MigrationsService.removedProperties) { + migrations.push( + new RemovePropertyMigration( + propertyName, + originalConfig, + migratedConfig + ) + ); + } + + for (const [ + oldPropertyName, + newPropertyName, + ] of MigrationsService.renamedProperties.entries()) { + migrations.push( + new RenamePropertyMigration( + oldPropertyName, + newPropertyName, + originalConfig, + migratedConfig + ) + ); + } + + migrations.push(new BinarySourceMigration(originalConfig, migratedConfig)); + migrations.push( + new IgnoreNodeModulesMigration(originalConfig, migratedConfig) + ); + migrations.push( + new RequiredStatusChecksMigration(originalConfig, migratedConfig) + ); + migrations.push(new TrustLevelMigration(originalConfig, migratedConfig)); + migrations.push(new GoModTidyMigration(originalConfig, migratedConfig)); + + return migrations; + } +} diff --git a/lib/config/migrations/required-status-checks-migration.spec.ts b/lib/config/migrations/required-status-checks-migration.spec.ts deleted file mode 100644 index afe9019ee573f3..00000000000000 --- a/lib/config/migrations/required-status-checks-migration.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { RequiredStatusChecksMigration } from './required-status-checks-migration'; - -describe('config/migrations/required-status-checks-migration', () => { - it('should migrate requiredStatusChecks=null to ignoreTests=true', () => { - const originalConfig: any = { - requiredStatusChecks: null, - }; - const migratedConfig: any = { - requiredStatusChecks: null, - }; - const migration = new RequiredStatusChecksMigration( - originalConfig, - migratedConfig - ); - - expect(migratedConfig.requiredStatusChecks).toBeNull(); - migration.migrate(); - expect(migratedConfig.requiredStatusChecks).toBeUndefined(); - expect(migratedConfig.ignoreTests).toBeTrue(); - }); -}); diff --git a/lib/config/migrations/required-status-checks-migration.ts b/lib/config/migrations/required-status-checks-migration.ts deleted file mode 100644 index 3f6d64e28fe7cf..00000000000000 --- a/lib/config/migrations/required-status-checks-migration.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Migration } from './migration'; - -export class RequiredStatusChecksMigration extends Migration { - public migrate(): void { - this.delete('requiredStatusChecks'); - - if (this.originalConfig.requiredStatusChecks === null) { - this.migratedConfig.ignoreTests = true; - } - } -} diff --git a/lib/config/migrations/types.ts b/lib/config/migrations/types.ts new file mode 100644 index 00000000000000..93f2035ed60a60 --- /dev/null +++ b/lib/config/migrations/types.ts @@ -0,0 +1,4 @@ +export interface Migration { + readonly propertyName: string; + run(): void; +} diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 786976d37d94da..230978b66338b2 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -262,7 +262,7 @@ const options: RenovateOptions[] = [ { name: 'binarySource', description: - 'Controls whether third party tools like npm or Gradle are called directly, or via Docker sidecar containers.', + 'Controls whether third-party tools like npm or Gradle are called directly, or via Docker sidecar containers.', globalOnly: true, type: 'string', allowedValues: ['global', 'docker'], @@ -548,6 +548,14 @@ const options: RenovateOptions[] = [ type: 'boolean', default: false, }, + { + name: 'allowPlugins', + description: + 'Configure this to true if repositories are allowed to run install plugins.', + globalOnly: true, + type: 'boolean', + default: false, + }, { name: 'allowScripts', description: @@ -564,6 +572,13 @@ const options: RenovateOptions[] = [ type: 'boolean', default: false, }, + { + name: 'ignorePlugins', + description: + 'Configure this to true if allowPlugins=true but you wish to skip running plugins when updating lock files.', + type: 'boolean', + default: false, + }, { name: 'ignoreScripts', description: @@ -732,6 +747,14 @@ const options: RenovateOptions[] = [ subType: 'string', default: [], }, + { + name: 'executionTimeout', + description: + 'Default execution timeout in minutes for child processes Renovate creates.', + type: 'integer', + default: 15, + globalOnly: true, + }, { name: 'aliases', description: 'Aliases for registries, package manager specific.', @@ -982,6 +1005,28 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, + { + name: 'replacementName', + description: + 'The name of the new dependency that replaces the old deprecated dependency.', + type: 'string', + stage: 'package', + parent: 'packageRules', + mergeable: true, + cli: false, + env: false, + }, + { + name: 'replacementVersion', + description: + 'The version of the new dependency that replaces the old deprecated dependency.', + type: 'string', + stage: 'package', + parent: 'packageRules', + mergeable: true, + cli: false, + env: false, + }, { name: 'matchUpdateTypes', description: @@ -1196,6 +1241,23 @@ const options: RenovateOptions[] = [ cli: false, mergeable: true, }, + { + name: 'replacement', + description: 'Configuration to apply when replacing a dependency.', + stage: 'package', + type: 'object', + default: { + branchTopic: '{{{depNameSanitized}}}-replacement', + commitMessageAction: 'Replace', + commitMessageExtra: + 'with {{newName}} {{#if isMajor}}v{{{newMajor}}}{{else}}{{#if isSingleVersion}}v{{{newVersion}}}{{else}}{{{newValue}}}{{/if}}{{/if}}', + prBodyNotes: [ + 'This is a special PR that replaces `{{{depNameSanitized}}}` with the community suggested minimal stable replacement version.', + ], + }, + cli: false, + mergeable: true, + }, // Semantic commit / Semantic release { name: 'semanticCommits', @@ -1613,6 +1675,14 @@ const options: RenovateOptions[] = [ default: false, supportedPlatforms: ['gitlab'], }, + { + name: 'confidential', + description: + 'If enabled, issues created by Renovate are set as confidential.', + type: 'boolean', + default: false, + supportedPlatforms: ['gitlab'], + }, { name: 'reviewersSampleSize', description: 'Take a random sample of given size from reviewers.', diff --git a/lib/config/presets/azure/__snapshots__/index.spec.ts.snap b/lib/config/presets/azure/__snapshots__/index.spec.ts.snap index c9758c77a590f4..862395350f3778 100644 --- a/lib/config/presets/azure/__snapshots__/index.spec.ts.snap +++ b/lib/config/presets/azure/__snapshots__/index.spec.ts.snap @@ -5,6 +5,13 @@ Array [ Array [ "123456", "some-filename.json", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, ], ] `; diff --git a/lib/config/presets/index.spec.ts b/lib/config/presets/index.spec.ts index c96efd8360884f..523484bdfc839e 100644 --- a/lib/config/presets/index.spec.ts +++ b/lib/config/presets/index.spec.ts @@ -63,9 +63,7 @@ describe('config/presets/index', () => { } expect(e).toBeDefined(); expect(e.validationSource).toBeUndefined(); - expect(e.validationError).toEqual( - "Cannot find preset's package (notfound)" - ); + expect(e.validationError).toBe("Cannot find preset's package (notfound)"); expect(e.validationMessage).toBeUndefined(); }); it('throws if invalid preset', async () => { @@ -79,7 +77,7 @@ describe('config/presets/index', () => { } expect(e).toBeDefined(); expect(e.validationSource).toBeUndefined(); - expect(e.validationError).toEqual( + expect(e.validationError).toBe( 'Preset name not found within published preset config (wrongpreset:invalid-preset)' ); expect(e.validationMessage).toBeUndefined(); @@ -96,9 +94,7 @@ describe('config/presets/index', () => { } expect(e).toBeDefined(); expect(e.validationSource).toBeUndefined(); - expect(e.validationError).toEqual( - 'Preset is invalid (github>user/repo//)' - ); + expect(e.validationError).toBe('Preset is invalid (github>user/repo//)'); expect(e.validationMessage).toBeUndefined(); }); @@ -113,7 +109,7 @@ describe('config/presets/index', () => { } expect(e).toBeDefined(); expect(e.validationSource).toBeUndefined(); - expect(e.validationError).toEqual( + expect(e.validationError).toBe( 'Sub-presets cannot be combined with a custom path (github>user/repo//path:subpreset)' ); expect(e.validationMessage).toBeUndefined(); @@ -130,7 +126,7 @@ describe('config/presets/index', () => { } expect(e).toBeDefined(); expect(e.validationSource).toBeUndefined(); - expect(e.validationError).toEqual( + expect(e.validationError).toBe( 'Preset package is missing a renovate-config entry (noconfig:base)' ); expect(e.validationMessage).toBeUndefined(); @@ -161,7 +157,7 @@ describe('config/presets/index', () => { ignoreDeps: [], rangeStrategy: 'pin', }); - expect(res.rangeStrategy).toEqual('pin'); + expect(res.rangeStrategy).toBe('pin'); }); it('throws if valid and invalid', async () => { config.foo = 1; @@ -174,7 +170,7 @@ describe('config/presets/index', () => { } expect(e).toBeDefined(); expect(e.validationSource).toBeUndefined(); - expect(e.validationError).toEqual( + expect(e.validationError).toBe( 'Preset name not found within published preset config (wrongpreset:invalid-preset)' ); expect(e.validationMessage).toBeUndefined(); @@ -233,7 +229,7 @@ describe('config/presets/index', () => { config.extends = ['ikatyang:library']; const res = await presets.resolveConfigPresets(config); expect(res).toMatchSnapshot(); - expect(res.automerge).not.toBeDefined(); + expect(res.automerge).toBeUndefined(); expect(res.minor.automerge).toBeTrue(); }); @@ -256,7 +252,7 @@ describe('config/presets/index', () => { expect(res.labels).toEqual(['self-hosted resolved']); expect(local.getPreset.mock.calls).toHaveLength(1); - expect(local.getPreset.mock.calls[0][0].baseConfig).not.toBeUndefined(); + expect(local.getPreset.mock.calls[0][0].baseConfig).toBeDefined(); expect(res).toMatchSnapshot(); }); }); @@ -269,12 +265,12 @@ describe('config/presets/index', () => { it('replaces args in strings', () => { const str = '{{arg2}} foo {{arg0}}{{arg1}}'; const res = presets.replaceArgs(str, argMappings); - expect(res).toEqual('c foo ab'); + expect(res).toBe('c foo ab'); }); it('replaces args twice in same string', () => { const str = '{{arg2}}{{arg0}} foo {{arg0}}{{arg1}}'; const res = presets.replaceArgs(str, argMappings); - expect(res).toEqual('ca foo ab'); + expect(res).toBe('ca foo ab'); }); it('replaces objects', () => { const obj = { @@ -322,6 +318,15 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('handles special chars', () => { + expect(presets.parsePreset('github>some/repo:foo+bar')).toEqual({ + packageName: 'some/repo', + params: undefined, + presetName: 'foo+bar', + presetPath: undefined, + presetSource: 'github', + }); + }); it('parses github subfiles', () => { expect(presets.parsePreset('github>some/repo:somefile')).toEqual({ packageName: 'some/repo', @@ -337,8 +342,8 @@ describe('config/presets/index', () => { ).toEqual({ packageName: 'some/repo', params: undefined, - presetName: 'somepreset', - presetPath: 'somefile', + presetName: 'somefile/somepreset', + presetPath: undefined, presetSource: 'github', }); }); @@ -350,8 +355,8 @@ describe('config/presets/index', () => { ).toEqual({ packageName: 'some/repo', params: undefined, - presetName: 'somesubpreset', - presetPath: 'somefile/somepreset', + presetName: 'somefile/somepreset/somesubpreset', + presetPath: undefined, presetSource: 'github', }); }); @@ -402,6 +407,15 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local with spaces', () => { + expect(presets.parsePreset('local>A2B CD/A2B_Renovate')).toEqual({ + packageName: 'A2B CD/A2B_Renovate', + params: undefined, + presetName: 'default', + presetPath: undefined, + presetSource: 'local', + }); + }); it('parses local with subdirectory', () => { expect( presets.parsePreset('local>some-group/some-repo//some-dir/some-file') @@ -413,6 +427,34 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local with sub preset and tag', () => { + expect( + presets.parsePreset( + 'local>some-group/some-repo:some-file/subpreset#1.2.3' + ) + ).toEqual({ + packageName: 'some-group/some-repo', + params: undefined, + presetName: 'some-file/subpreset', + presetPath: undefined, + presetSource: 'local', + packageTag: '1.2.3', + }); + }); + it('parses local with subdirectory and tag', () => { + expect( + presets.parsePreset( + 'local>some-group/some-repo//some-dir/some-file#1.2.3' + ) + ).toEqual({ + packageName: 'some-group/some-repo', + params: undefined, + presetName: 'some-file', + presetPath: 'some-dir', + presetSource: 'local', + packageTag: '1.2.3', + }); + }); it('parses no prefix as local', () => { expect(presets.parsePreset('some/repo')).toEqual({ packageName: 'some/repo', diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index d60baf3122ecb4..4dc90acb3a3acd 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -5,6 +5,7 @@ import { } from '../../constants/error-messages'; import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; +import { clone } from '../../util/clone'; import { regEx } from '../../util/regex'; import * as massage from '../massage'; import * as migration from '../migration'; @@ -39,7 +40,7 @@ const nonScopedPresetWithSubdirRegex = regEx( /^(?~?[\w\-./]+?)\/\/(?:(?[\w\-./]+)\/)?(?[\w\-.]+)(?:#(?[\w\-.]+?))?$/ ); const gitPresetRegex = regEx( - /^(?[\w\-./]+)(?::(?[\w-./]+\/))?(?::?(?[\w\-.]+))?(?:#(?[\w\-.]+?))?$/ + /^(?[\w\-. /]+)(?::(?[\w\-.+/]+))?(?:#(?[\w\-.]+?))?$/ ); export function replaceArgs( @@ -74,10 +75,10 @@ export function replaceArgs( export function parsePreset(input: string): ParsedPreset { let str = input; let presetSource: string; - let presetPath: string; + let presetPath: string | undefined; let packageName: string; let presetName: string; - let packageTag: string; + let packageTag: string | undefined; let params: string[]; if (str.startsWith('github>')) { presetSource = 'github'; @@ -119,6 +120,7 @@ export function parsePreset(input: string): ParsedPreset { 'packages', 'preview', 'regexManagers', + 'replacements', 'schedule', 'workarounds', ]; @@ -157,13 +159,9 @@ export function parsePreset(input: string): ParsedPreset { ({ packageName, presetPath, presetName, packageTag } = nonScopedPresetWithSubdirRegex.exec(str)?.groups || {}); } else { - ({ packageName, presetPath, presetName, packageTag } = + ({ packageName, presetName, packageTag } = gitPresetRegex.exec(str)?.groups || {}); - if (is.nonEmptyString(presetPath) && presetPath.endsWith('/')) { - presetPath = presetPath.slice(0, -1); - } - if (presetSource === 'npm' && !packageName.startsWith('renovate-config-')) { packageName = `renovate-config-${packageName}`; } @@ -251,11 +249,12 @@ export async function getPreset( export async function resolveConfigPresets( inputConfig: AllConfig, baseConfig?: RenovateConfig, - ignorePresets?: string[], + _ignorePresets?: string[], existingPresets: string[] = [] ): Promise { + let ignorePresets = clone(_ignorePresets); if (!ignorePresets || ignorePresets.length === 0) { - ignorePresets = inputConfig.ignorePresets || []; // eslint-disable-line + ignorePresets = inputConfig.ignorePresets || []; } logger.trace( { config: inputConfig, existingPresets }, diff --git a/lib/config/presets/internal/index.ts b/lib/config/presets/internal/index.ts index e152b9747e19d7..b0dd3dcbe44d55 100644 --- a/lib/config/presets/internal/index.ts +++ b/lib/config/presets/internal/index.ts @@ -10,6 +10,7 @@ import * as npm from './npm'; import * as packagesPreset from './packages'; import * as previewPreset from './preview'; import * as regexManagersPreset from './regex-managers'; +import * as replacements from './replacements'; import * as schedulePreset from './schedule'; import * as workaroundsPreset from './workarounds'; @@ -25,6 +26,7 @@ export const groups: Record> = { packages: packagesPreset.presets, preview: previewPreset.presets, regexManagers: regexManagersPreset.presets, + replacements: replacements.presets, schedule: schedulePreset.presets, workarounds: workaroundsPreset.presets, }; diff --git a/lib/config/presets/internal/monorepo.ts b/lib/config/presets/internal/monorepo.ts index 4bf38f6d1e2bb0..191f501cf4f83b 100644 --- a/lib/config/presets/internal/monorepo.ts +++ b/lib/config/presets/internal/monorepo.ts @@ -34,6 +34,7 @@ const repoGroups = { deno: 'https://github.com/denoland/deno', 'devextreme-reactive': 'https://github.com/DevExpress/devextreme-reactive', 'dnd-kit': 'https://github.com/clauderic/dnd-kit', + 'elastic-apm-agent-rum-js': 'https://github.com/elastic/apm-agent-rum-js', 'electron-forge': 'https://github.com/electron-userland/electron-forge', 'ember-decorators': 'https://github.com/ember-decorators/ember-decorators', 'graphql-modules': 'https://github.com/Urigo/graphql-modules', @@ -148,6 +149,7 @@ const repoGroups = { 'opentelemetry-js': 'https://github.com/open-telemetry/opentelemetry-js', 'opentelemetry-dotnet': 'https://github.com/open-telemetry/opentelemetry-dotnet', + 'opentelemetry-go': 'https://github.com/open-telemetry/opentelemetry-go', picassojs: 'https://github.com/qlik-oss/picasso.js', pnpjs: 'https://github.com/pnp/pnpjs', playwright: 'https://github.com/Microsoft/playwright', @@ -182,6 +184,7 @@ const repoGroups = { webdriverio: 'https://github.com/webdriverio/webdriverio', workbox: 'https://github.com/googlechrome/workbox', vstest: 'https://github.com/microsoft/vstest', + xterm: 'https://github.com/xtermjs/xterm.js', }; const patternGroups = { diff --git a/lib/config/presets/internal/replacements.ts b/lib/config/presets/internal/replacements.ts new file mode 100644 index 00000000000000..a610b49b788fe5 --- /dev/null +++ b/lib/config/presets/internal/replacements.ts @@ -0,0 +1,45 @@ +import type { Preset } from '../types'; + +export const presets: Record = { + all: { + description: 'All replacements', + extends: [ + 'replacements:jade-to-pug', + 'replacements:cucumber-to-scoped', + 'replacements:rollup-node-resolve-to-scoped', + ], + }, + 'jade-to-pug': { + description: 'Jade was renamed to Pug', + packageRules: [ + { + matchDatasources: ['npm'], + matchPackageNames: ['jade'], + replacementName: 'pug', + replacementVersion: '2.0.0', + }, + ], + }, + 'cucumber-to-scoped': { + description: 'cucumber became scoped', + packageRules: [ + { + matchDatasources: ['npm'], + matchPackageNames: ['cucumber'], + replacementName: '@cucumber/cucumber', + replacementVersion: '7.0.0', + }, + ], + }, + 'rollup-node-resolve-to-scoped': { + description: 'the node-resolve plugin for rollup became scoped', + packageRules: [ + { + matchDatasources: ['npm'], + matchPackageNames: ['rollup-plugin-node-resolve'], + replacementName: '@rollup/plugin-node-resolve', + replacementVersion: '6.0.0', + }, + ], + }, +}; diff --git a/lib/config/presets/npm/index.spec.ts b/lib/config/presets/npm/index.spec.ts index 9ec951dd424a62..3c55df68bb7122 100644 --- a/lib/config/presets/npm/index.spec.ts +++ b/lib/config/presets/npm/index.spec.ts @@ -1,5 +1,5 @@ import * as httpMock from '../../../../test/http-mock'; -import { setGlobalConfig } from '../../global'; +import { GlobalConfig } from '../../global'; import * as npm from '.'; jest.mock('registry-auth-token'); @@ -8,7 +8,7 @@ jest.mock('delay'); describe('config/presets/npm/index', () => { beforeEach(() => { jest.resetAllMocks(); - setGlobalConfig(); + GlobalConfig.reset(); }); afterEach(() => { delete process.env.RENOVATE_CACHE_NPM_MINUTES; diff --git a/lib/config/presets/util.ts b/lib/config/presets/util.ts index 8b044b5a33e236..f0a014409600a6 100644 --- a/lib/config/presets/util.ts +++ b/lib/config/presets/util.ts @@ -14,12 +14,11 @@ export async function fetchPreset({ pkgName, filePreset, presetPath, - endpoint, + endpoint: _endpoint, packageTag = null, fetch, }: FetchPresetConfig): Promise { - // eslint-disable-next-line no-param-reassign - endpoint = ensureTrailingSlash(endpoint); + const endpoint = ensureTrailingSlash(_endpoint); const [fileName, presetName, subPresetName] = filePreset.split('/'); const pathPrefix = presetPath ? `${presetPath}/` : ''; const buildFilePath = (name: string): string => `${pathPrefix}${name}`; diff --git a/lib/config/types.ts b/lib/config/types.ts index 07cbe71eb1d6e2..c349a7066c016a 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -1,7 +1,7 @@ import type { LogLevel } from 'bunyan'; import type { Range } from 'semver'; import type { HostRule } from '../types'; -import type { GitNoVerifyOption } from '../util/git'; +import type { GitNoVerifyOption } from '../util/git/types'; export type RenovateConfigStage = | 'global' @@ -27,6 +27,7 @@ export interface RenovateSharedConfig { manager?: string; commitMessage?: string; commitMessagePrefix?: string; + confidential?: boolean; draftPR?: boolean; enabled?: boolean; enabledManagers?: string[]; @@ -91,6 +92,7 @@ export interface GlobalOnlyConfig { // The below should contain config options where globalOnly=true export interface RepoGlobalConfig { allowCustomCrateRegistries?: boolean; + allowPlugins?: boolean; allowPostUpgradeCommandTemplating?: boolean; allowScripts?: boolean; allowedPostUpgradeCommands?: string[]; @@ -100,6 +102,7 @@ export interface RepoGlobalConfig { dockerImagePrefix?: string; dockerUser?: string; dryRun?: boolean; + executionTimeout?: number; exposeAllEnv?: boolean; migratePresets?: Record; privateKey?: string; @@ -232,7 +235,8 @@ export type UpdateType = | 'lockFileMaintenance' | 'lockfileUpdate' | 'rollback' - | 'bump'; + | 'bump' + | 'replacement'; export type MatchStringsStrategy = 'any' | 'recursive' | 'combination'; diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index eaa44fec3bf26d..ea0566e943ac39 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -7,12 +7,10 @@ describe('config/validation', () => { expect(configValidation.getParentName('encrypted')).toBeEmptyString(); }); it('handles array types', () => { - expect(configValidation.getParentName('hostRules[1]')).toEqual( - 'hostRules' - ); + expect(configValidation.getParentName('hostRules[1]')).toBe('hostRules'); }); it('handles encrypted within array types', () => { - expect(configValidation.getParentName('hostRules[0].encrypted')).toEqual( + expect(configValidation.getParentName('hostRules[0].encrypted')).toBe( 'hostRules' ); }); @@ -298,16 +296,54 @@ describe('config/validation', () => { regexManagers: [ { fileMatch: [], - matchStrings: [], }, ], }; const { warnings, errors } = await configValidation.validateConfig( - config, + config as any, true ); expect(warnings).toHaveLength(0); expect(errors).toHaveLength(1); + expect(errors).toMatchInlineSnapshot(` + Array [ + Object { + "message": "Each Regex Manager must contain a non-empty fileMatch array", + "topic": "Configuration Error", + }, + ] + `); + }); + it('errors if empty regexManager matchStrings', async () => { + const config = { + regexManagers: [ + { + fileMatch: ['foo'], + matchStrings: [], + }, + { + fileMatch: ['foo'], + }, + ], + }; + const { warnings, errors } = await configValidation.validateConfig( + config as RenovateConfig, + true + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(2); + expect(errors).toMatchInlineSnapshot(` + Array [ + Object { + "message": "Each Regex Manager must contain a non-empty matchStrings array", + "topic": "Configuration Error", + }, + Object { + "message": "Each Regex Manager must contain a non-empty matchStrings array", + "topic": "Configuration Error", + }, + ] + `); }); it('errors if no regexManager fileMatch', async () => { const config = { diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 3faa3dbd77a292..4c80f07031328e 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -122,7 +122,7 @@ export async function validateConfig( topic: 'Config security error', message: '__proto__', }); - continue; // eslint-disable-line + continue; } if (parentPath && topLevelObjects.includes(key)) { errors.push({ @@ -228,7 +228,7 @@ export async function validateConfig( message: `${currentPath}: ${errorMessage}`, }); } - } else if (val != null) { + } else if (val !== null) { const type = optionTypes[key]; if (type === 'boolean') { if (val !== true && val !== false) { @@ -408,44 +408,51 @@ export async function validateConfig( )}`, }); } else if (is.nonEmptyArray(regexManager.fileMatch)) { - let validRegex = false; - for (const matchString of regexManager.matchStrings) { - try { - regEx(matchString); - validRegex = true; - } catch (e) { - errors.push({ - topic: 'Configuration Error', - message: `Invalid regExp for ${currentPath}: \`${String( - matchString - )}\``, - }); - } - } - if (validRegex) { - const mandatoryFields = [ - 'depName', - 'currentValue', - 'datasource', - ]; - for (const field of mandatoryFields) { - if ( - !regexManager[`${field}Template`] && - !regexManager.matchStrings.some((matchString) => - matchString.includes(`(?<${field}>`) - ) - ) { + if (is.nonEmptyArray(regexManager.matchStrings)) { + let validRegex = false; + for (const matchString of regexManager.matchStrings) { + try { + regEx(matchString); + validRegex = true; + } catch (e) { errors.push({ topic: 'Configuration Error', - message: `Regex Managers must contain ${field}Template configuration or regex group named ${field}`, + message: `Invalid regExp for ${currentPath}: \`${String( + matchString + )}\``, }); } } + if (validRegex) { + const mandatoryFields = [ + 'depName', + 'currentValue', + 'datasource', + ]; + for (const field of mandatoryFields) { + if ( + !regexManager[`${field}Template`] && + !regexManager.matchStrings.some((matchString) => + matchString.includes(`(?<${field}>`) + ) + ) { + errors.push({ + topic: 'Configuration Error', + message: `Regex Managers must contain ${field}Template configuration or regex group named ${field}`, + }); + } + } + } + } else { + errors.push({ + topic: 'Configuration Error', + message: `Each Regex Manager must contain a non-empty matchStrings array`, + }); } } else { errors.push({ topic: 'Configuration Error', - message: `Each Regex Manager must contain a fileMatch array`, + message: `Each Regex Manager must contain a non-empty fileMatch array`, }); } } diff --git a/lib/datasource/__snapshots__/metadata.spec.ts.snap b/lib/datasource/__snapshots__/metadata.spec.ts.snap index d114fca01e395d..8c58352c4efc18 100644 --- a/lib/datasource/__snapshots__/metadata.spec.ts.snap +++ b/lib/datasource/__snapshots__/metadata.spec.ts.snap @@ -135,3 +135,27 @@ Object { "sourceUrl": "https://gitlab.com/meno/dropzone", } `; + +exports[`datasource/metadata Should massage github sourceUrls 1`] = ` +Object { + "releases": Array [ + Object { + "releaseTimestamp": "2018-07-13T10:14:17.000Z", + "version": "2.0.0", + }, + Object { + "releaseTimestamp": "2017-10-24T10:09:16.000Z", + "version": "2.0.0.dev1", + }, + Object { + "releaseTimestamp": "2019-01-20T19:59:28.000Z", + "version": "2.1.0", + }, + Object { + "releaseTimestamp": "2019-07-16T18:29:00.000Z", + "version": "2.2.0", + }, + ], + "sourceUrl": "https://github.com/some/repo", +} +`; diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts index 6469279f8c6cdf..8b2f7004dbaa94 100644 --- a/lib/datasource/api.ts +++ b/lib/datasource/api.ts @@ -1,5 +1,6 @@ import { AdoptiumJavaDatasource } from './adoptium-java'; import { ArtifactoryDatasource } from './artifactory'; +import { AwsMachineImageDataSource } from './aws-machine-image'; import { BitBucketTagsDatasource } from './bitbucket-tags'; import { CdnJsDatasource } from './cdnjs'; import { ClojureDatasource } from './clojure'; @@ -19,7 +20,7 @@ import * as go from './go'; import { GradleVersionDatasource } from './gradle-version'; import { HelmDatasource } from './helm'; import { HexDatasource } from './hex'; -import * as jenkinsPlugins from './jenkins-plugins'; +import { JenkinsPluginsDatasource } from './jenkins-plugins'; import * as maven from './maven'; import { NodeDatasource } from './node'; import * as npm from './npm'; @@ -30,7 +31,7 @@ import * as pod from './pod'; import { PypiDatasource } from './pypi'; import * as repology from './repology'; import { RubyVersionDatasource } from './ruby-version'; -import * as rubygems from './rubygems'; +import { RubyGemsDatasource } from './rubygems'; import * as sbtPackage from './sbt-package'; import * as sbtPlugin from './sbt-plugin'; import { TerraformModuleDatasource } from './terraform-module'; @@ -42,6 +43,7 @@ export default api; api.set(AdoptiumJavaDatasource.id, new AdoptiumJavaDatasource()); api.set(ArtifactoryDatasource.id, new ArtifactoryDatasource()); +api.set(AwsMachineImageDataSource.id, new AwsMachineImageDataSource()); api.set('bitbucket-tags', new BitBucketTagsDatasource()); api.set('cdnjs', new CdnJsDatasource()); api.set('clojure', new ClojureDatasource()); @@ -61,7 +63,7 @@ api.set('go', go); api.set('gradle-version', new GradleVersionDatasource()); api.set('helm', new HelmDatasource()); api.set('hex', new HexDatasource()); -api.set('jenkins-plugins', jenkinsPlugins); +api.set('jenkins-plugins', new JenkinsPluginsDatasource()); api.set('maven', maven); api.set('npm', npm); api.set(NodeDatasource.id, new NodeDatasource()); @@ -72,7 +74,7 @@ api.set('pod', pod); api.set('pypi', new PypiDatasource()); api.set('repology', repology); api.set('ruby-version', new RubyVersionDatasource()); -api.set('rubygems', rubygems); +api.set(RubyGemsDatasource.id, new RubyGemsDatasource()); api.set('sbt-package', sbtPackage); api.set('sbt-plugin', sbtPlugin); api.set('terraform-module', new TerraformModuleDatasource()); diff --git a/lib/datasource/artifactory/index.ts b/lib/datasource/artifactory/index.ts index 3b8054e905eccb..95744dcb28d466 100644 --- a/lib/datasource/artifactory/index.ts +++ b/lib/datasource/artifactory/index.ts @@ -109,6 +109,6 @@ export class ArtifactoryDatasource extends Datasource { } private static parseReleaseTimestamp(rawText: string): string { - return rawText.trim().replace(regEx(/ ?-$/), ''); // TODO #12071 + return rawText.trim().replace(regEx(/ ?-$/), '') + 'Z'; // TODO #12071 } } diff --git a/lib/datasource/artifactory/readme.md b/lib/datasource/artifactory/readme.md index e8e40c6fe46d76..c1c49534bbe1b3 100644 --- a/lib/datasource/artifactory/readme.md +++ b/lib/datasource/artifactory/readme.md @@ -3,3 +3,5 @@ Artifactory is the recommended registry for Conan packages. This datasource returns releases from given custom `registryUrl`(s). The target URL is composed by the `registryUrl` and the `lookupName`, which defaults to `depName` when `lookupName` is not defined. + +The release timestamp is taken from the date in the directory listing, and is assumed to be in UTC time. diff --git a/lib/datasource/aws-machine-image/__snapshots__/index.spec.ts.snap b/lib/datasource/aws-machine-image/__snapshots__/index.spec.ts.snap new file mode 100644 index 00000000000000..72338b167d0f2b --- /dev/null +++ b/lib/datasource/aws-machine-image/__snapshots__/index.spec.ts.snap @@ -0,0 +1,628 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`datasource/aws-machine-image/index getSortedAwsMachineImages() with 1 returned image 1`] = ` +Array [ + Object { + "args": Array [ + DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "1image", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + ], + "callId": 1, + "callback": undefined, + "errorWithCallStack": [Error], + "exception": undefined, + "firstArg": DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "1image", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + "lastArg": DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "1image", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + "proxy": [Function], + "returnValue": Promise {}, + "thisValue": EC2Client { + "config": Object { + "apiVersion": "2016-11-15", + "base64Decoder": [Function], + "base64Encoder": [Function], + "bodyLengthChecker": [Function], + "credentialDefaultProvider": [Function], + "credentials": [Function], + "customUserAgent": undefined, + "defaultUserAgentProvider": [Function], + "disableHostPrefix": false, + "endpoint": [Function], + "isCustomEndpoint": false, + "logger": Object {}, + "maxAttempts": [Function], + "region": [Function], + "regionInfoProvider": [Function], + "requestHandler": NodeHttpHandler { + "connectionTimeout": undefined, + "httpAgent": Agent { + "_events": Object { + "free": [Function], + "newListener": [Function], + }, + "_eventsCount": 2, + "_maxListeners": undefined, + "defaultPort": 80, + "freeSockets": Object {}, + "keepAlive": true, + "keepAliveMsecs": 1000, + "maxFreeSockets": 256, + "maxSockets": 50, + "maxTotalSockets": Infinity, + "options": Object { + "keepAlive": true, + "maxSockets": 50, + "path": null, + }, + "protocol": "http:", + "requests": Object {}, + "scheduling": "lifo", + "sockets": Object {}, + "totalSocketCount": 0, + Symbol(kCapture): false, + }, + "httpsAgent": Agent { + "_events": Object { + "free": [Function], + "newListener": [Function], + }, + "_eventsCount": 2, + "_maxListeners": undefined, + "_sessionCache": Object { + "list": Array [], + "map": Object {}, + }, + "defaultPort": 443, + "freeSockets": Object {}, + "keepAlive": true, + "keepAliveMsecs": 1000, + "maxCachedSessions": 100, + "maxFreeSockets": 256, + "maxSockets": 50, + "maxTotalSockets": Infinity, + "options": Object { + "keepAlive": true, + "maxSockets": 50, + "path": null, + }, + "protocol": "https:", + "requests": Object {}, + "scheduling": "lifo", + "sockets": Object {}, + "totalSocketCount": 0, + Symbol(kCapture): false, + }, + "metadata": Object { + "handlerProtocol": "http/1.1", + }, + "socketTimeout": undefined, + }, + "retryMode": [Function], + "retryStrategy": [Function], + "runtime": "node", + "serviceId": "EC2", + "sha256": [Function], + "signer": [Function], + "signingEscapePath": true, + "streamCollector": [Function], + "systemClockOffset": 0, + "tls": true, + "urlParser": [Function], + "utf8Decoder": [Function], + "utf8Encoder": [Function], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + }, +] +`; + +exports[`datasource/aws-machine-image/index getSortedAwsMachineImages() with 3 returned images 1`] = ` +Array [ + Object { + "args": Array [ + DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "3images", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + ], + "callId": 0, + "callback": undefined, + "errorWithCallStack": [Error], + "exception": undefined, + "firstArg": DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "3images", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + "lastArg": DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "3images", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + "proxy": [Function], + "returnValue": Promise {}, + "thisValue": EC2Client { + "config": Object { + "apiVersion": "2016-11-15", + "base64Decoder": [Function], + "base64Encoder": [Function], + "bodyLengthChecker": [Function], + "credentialDefaultProvider": [Function], + "credentials": [Function], + "customUserAgent": undefined, + "defaultUserAgentProvider": [Function], + "disableHostPrefix": false, + "endpoint": [Function], + "isCustomEndpoint": false, + "logger": Object {}, + "maxAttempts": [Function], + "region": [Function], + "regionInfoProvider": [Function], + "requestHandler": NodeHttpHandler { + "connectionTimeout": undefined, + "httpAgent": Agent { + "_events": Object { + "free": [Function], + "newListener": [Function], + }, + "_eventsCount": 2, + "_maxListeners": undefined, + "defaultPort": 80, + "freeSockets": Object {}, + "keepAlive": true, + "keepAliveMsecs": 1000, + "maxFreeSockets": 256, + "maxSockets": 50, + "maxTotalSockets": Infinity, + "options": Object { + "keepAlive": true, + "maxSockets": 50, + "path": null, + }, + "protocol": "http:", + "requests": Object {}, + "scheduling": "lifo", + "sockets": Object {}, + "totalSocketCount": 0, + Symbol(kCapture): false, + }, + "httpsAgent": Agent { + "_events": Object { + "free": [Function], + "newListener": [Function], + }, + "_eventsCount": 2, + "_maxListeners": undefined, + "_sessionCache": Object { + "list": Array [], + "map": Object {}, + }, + "defaultPort": 443, + "freeSockets": Object {}, + "keepAlive": true, + "keepAliveMsecs": 1000, + "maxCachedSessions": 100, + "maxFreeSockets": 256, + "maxSockets": 50, + "maxTotalSockets": Infinity, + "options": Object { + "keepAlive": true, + "maxSockets": 50, + "path": null, + }, + "protocol": "https:", + "requests": Object {}, + "scheduling": "lifo", + "sockets": Object {}, + "totalSocketCount": 0, + Symbol(kCapture): false, + }, + "metadata": Object { + "handlerProtocol": "http/1.1", + }, + "socketTimeout": undefined, + }, + "retryMode": [Function], + "retryStrategy": [Function], + "runtime": "node", + "serviceId": "EC2", + "sha256": [Function], + "signer": [Function], + "signingEscapePath": true, + "streamCollector": [Function], + "systemClockOffset": 0, + "tls": true, + "urlParser": [Function], + "utf8Decoder": [Function], + "utf8Encoder": [Function], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + }, +] +`; + +exports[`datasource/aws-machine-image/index getSortedAwsMachineImages() without returned images 1`] = ` +Array [ + Object { + "args": Array [ + DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "noiamge", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + ], + "callId": 2, + "callback": undefined, + "errorWithCallStack": [Error], + "exception": undefined, + "firstArg": DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "noiamge", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + "lastArg": DescribeImagesCommand { + "input": Object { + "Filters": Array [ + Object { + "Name": "owner-id", + "Values": Array [ + "602401143452", + ], + }, + Object { + "Name": "name", + "Values": Array [ + "noiamge", + ], + }, + ], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + "proxy": [Function], + "returnValue": Promise {}, + "thisValue": EC2Client { + "config": Object { + "apiVersion": "2016-11-15", + "base64Decoder": [Function], + "base64Encoder": [Function], + "bodyLengthChecker": [Function], + "credentialDefaultProvider": [Function], + "credentials": [Function], + "customUserAgent": undefined, + "defaultUserAgentProvider": [Function], + "disableHostPrefix": false, + "endpoint": [Function], + "isCustomEndpoint": false, + "logger": Object {}, + "maxAttempts": [Function], + "region": [Function], + "regionInfoProvider": [Function], + "requestHandler": NodeHttpHandler { + "connectionTimeout": undefined, + "httpAgent": Agent { + "_events": Object { + "free": [Function], + "newListener": [Function], + }, + "_eventsCount": 2, + "_maxListeners": undefined, + "defaultPort": 80, + "freeSockets": Object {}, + "keepAlive": true, + "keepAliveMsecs": 1000, + "maxFreeSockets": 256, + "maxSockets": 50, + "maxTotalSockets": Infinity, + "options": Object { + "keepAlive": true, + "maxSockets": 50, + "path": null, + }, + "protocol": "http:", + "requests": Object {}, + "scheduling": "lifo", + "sockets": Object {}, + "totalSocketCount": 0, + Symbol(kCapture): false, + }, + "httpsAgent": Agent { + "_events": Object { + "free": [Function], + "newListener": [Function], + }, + "_eventsCount": 2, + "_maxListeners": undefined, + "_sessionCache": Object { + "list": Array [], + "map": Object {}, + }, + "defaultPort": 443, + "freeSockets": Object {}, + "keepAlive": true, + "keepAliveMsecs": 1000, + "maxCachedSessions": 100, + "maxFreeSockets": 256, + "maxSockets": 50, + "maxTotalSockets": Infinity, + "options": Object { + "keepAlive": true, + "maxSockets": 50, + "path": null, + }, + "protocol": "https:", + "requests": Object {}, + "scheduling": "lifo", + "sockets": Object {}, + "totalSocketCount": 0, + Symbol(kCapture): false, + }, + "metadata": Object { + "handlerProtocol": "http/1.1", + }, + "socketTimeout": undefined, + }, + "retryMode": [Function], + "retryStrategy": [Function], + "runtime": "node", + "serviceId": "EC2", + "sha256": [Function], + "signer": [Function], + "signingEscapePath": true, + "streamCollector": [Function], + "systemClockOffset": 0, + "tls": true, + "urlParser": [Function], + "utf8Decoder": [Function], + "utf8Encoder": [Function], + }, + "middlewareStack": Object { + "add": [Function], + "addRelativeTo": [Function], + "applyToStack": [Function], + "clone": [Function], + "concat": [Function], + "remove": [Function], + "removeByTag": [Function], + "resolve": [Function], + "use": [Function], + }, + }, + }, +] +`; diff --git a/lib/datasource/aws-machine-image/index.spec.ts b/lib/datasource/aws-machine-image/index.spec.ts new file mode 100644 index 00000000000000..419f4b318a1376 --- /dev/null +++ b/lib/datasource/aws-machine-image/index.spec.ts @@ -0,0 +1,296 @@ +import { + DescribeImagesCommand, + DescribeImagesResult, + EC2Client, + Image, +} from '@aws-sdk/client-ec2'; +import { mockClient } from 'aws-sdk-client-mock'; +import { getDigest, getPkgReleases } from '..'; +import { AwsMachineImageDataSource } from '.'; + +const datasource = AwsMachineImageDataSource.id; +const ec2Mock = mockClient(EC2Client); + +/** + * Testdata for mock implementation of EC2Client + * image1 to image3 from oldest to newest image + */ +const image1: Image = { + Architecture: 'x86_64', + CreationDate: '2021-08-13T17:47:12.000Z', + ImageId: 'ami-02ce3d9008cab69cb', + ImageLocation: 'amazon/amazon-eks-node-1.21-v20210813', + ImageType: 'machine', + Public: true, + OwnerId: '602401143452', + PlatformDetails: 'Linux/UNIX', + UsageOperation: 'RunInstances', + State: 'available', + BlockDeviceMappings: [ + { + DeviceName: '/dev/xvda', + Ebs: { + DeleteOnTermination: true, + SnapshotId: 'snap-0546beb61976e8017', + VolumeSize: 20, + VolumeType: 'gp2', + Encrypted: false, + }, + }, + ], + Description: + 'EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.21.2, docker: 19.03.13ce-1.amzn2, containerd: 1.4.6-2.amzn2)', + EnaSupport: true, + Hypervisor: 'xen', + ImageOwnerAlias: 'amazon', + Name: 'amazon-eks-node-1.21-v20210813', + RootDeviceName: '/dev/xvda', + RootDeviceType: 'ebs', + SriovNetSupport: 'simple', + VirtualizationType: 'hvm', +}; + +const image2: Image = { + Architecture: 'x86_64', + CreationDate: '2021-08-26T19:31:41.000Z', + DeprecationTime: '2021-08-14T17:47:12.000Z', + ImageId: 'ami-020d418c09883b165', + ImageLocation: 'amazon/amazon-eks-node-1.21-v20210826', + ImageType: 'machine', + Public: true, + OwnerId: '602401143452', + PlatformDetails: 'Linux/UNIX', + UsageOperation: 'RunInstances', + State: 'available', + BlockDeviceMappings: [ + { + DeviceName: '/dev/xvda', + Ebs: { + DeleteOnTermination: true, + SnapshotId: 'snap-01ba16a8ec8087603', + VolumeSize: 20, + VolumeType: 'gp2', + Encrypted: false, + }, + }, + ], + Description: + 'EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.21.2, docker: 19.03.13ce-1.amzn2, containerd: 1.4.6-2.amzn2)', + EnaSupport: true, + Hypervisor: 'xen', + ImageOwnerAlias: 'amazon', + Name: 'amazon-eks-node-1.21-v20210826', + RootDeviceName: '/dev/xvda', + RootDeviceType: 'ebs', + SriovNetSupport: 'simple', + VirtualizationType: 'hvm', +}; + +const image3: Image = { + Architecture: 'x86_64', + CreationDate: '2021-09-14T22:00:24.000Z', + ImageId: 'ami-05f83986b0fe58ada', + ImageLocation: 'amazon/amazon-eks-node-1.21-v20210914', + ImageType: 'machine', + Public: true, + OwnerId: '602401143452', + PlatformDetails: 'Linux/UNIX', + UsageOperation: 'RunInstances', + State: 'available', + BlockDeviceMappings: [ + { + DeviceName: '/dev/xvda', + Ebs: { + DeleteOnTermination: true, + SnapshotId: 'snap-0c6f79c3983fd8e1a', + VolumeSize: 20, + VolumeType: 'gp2', + Encrypted: false, + }, + }, + ], + Description: + 'EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.21.2, docker: 19.03.13ce-1.amzn2, containerd: 1.4.6-2.amzn2)', + EnaSupport: true, + Hypervisor: 'xen', + ImageOwnerAlias: 'amazon', + Name: 'amazon-eks-node-1.21-v20210914', + RootDeviceName: '/dev/xvda', + RootDeviceType: 'ebs', + SriovNetSupport: 'simple', + VirtualizationType: 'hvm', +}; + +const mock3Images: DescribeImagesResult = { + Images: [image3, image1, image2], +}; + +const mock1Image: DescribeImagesResult = { + Images: [image3], +}; + +const mockEmpty: DescribeImagesResult = {}; + +describe('datasource/aws-machine-image/index', () => { + describe('getSortedAwsMachineImages()', () => { + it('with 3 returned images', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock3Images); + const ec2DataSource = new AwsMachineImageDataSource(); + const res = await ec2DataSource.getSortedAwsMachineImages( + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["3images"]}]' + ); + expect(res).toStrictEqual([image1, image2, image3]); + expect(ec2Mock.calls()).toMatchSnapshot(); + }); + it('with 1 returned image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock1Image); + const ec2DataSource = new AwsMachineImageDataSource(); + const res = await ec2DataSource.getSortedAwsMachineImages( + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["1image"]}]' + ); + expect(res).toStrictEqual([image3]); + expect(ec2Mock.calls()).toMatchSnapshot(); + }); + it('without returned images', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mockEmpty); + const ec2DataSource = new AwsMachineImageDataSource(); + const res = await ec2DataSource.getSortedAwsMachineImages( + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["noiamge"]}]' + ); + expect(res).toStrictEqual([]); + expect(ec2Mock.calls()).toMatchSnapshot(); + }); + }); + + describe('getDigest()', () => { + it('without newValue, without returned images to be null', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mockEmpty); + const res = await getDigest({ + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without newValue, without returned images to be null"]}]', + }); + expect(res).toBeNull(); + }); + it('without newValue, with one matching image to return that image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock1Image); + const res = await getDigest({ + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without newValue, with one matching image to return that image"]}]', + }); + expect(res).toStrictEqual(image3.Name); + }); + it('without newValue, with 3 matching image to return the newest image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock3Images); + const res = await getDigest({ + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without newValue, with 3 matching image to return the newest image"]}]', + }); + expect(res).toStrictEqual(image3.Name); + }); + it('with matching newValue, with 3 matching image to return the matching image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock3Images); + const res = await getDigest( + { + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with matching newValue, with 3 matching image to return the matching image"]}]', + }, + image1.ImageId + ); + expect(res).toStrictEqual(image1.Name); + }); + it('with not matching newValue, with 3 matching images to return the matching image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock3Images); + const res = await getDigest( + { + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with not matching newValue, with 3 matching images to return the matching image"]}]', + }, + 'will never match' + ); + expect(res).toBeNull(); + }); + }); + + describe('getPkgReleases()', () => { + it('without returned images to be null', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mockEmpty); + const res = await getPkgReleases({ + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without returned images to be null"]}]', + }); + expect(res).toBeNull(); + }); + it('with one matching image to return that image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock1Image); + const res = await getPkgReleases({ + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with one matching image to return that image"]}]', + }); + expect(res).toStrictEqual({ + releases: [ + { + isDeprecated: false, + newDigest: image3.Name, + releaseTimestamp: image3.CreationDate, + version: image3.ImageId, + }, + ], + }); + }); + it('with one deprecated matching image to return that image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves({ Images: [image2] }); + const res = await getPkgReleases({ + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with one deprecated matching image to return that image"]}]', + }); + expect(res).toStrictEqual({ + releases: [ + { + isDeprecated: true, + newDigest: image2.Name, + releaseTimestamp: image2.CreationDate, + version: image2.ImageId, + }, + ], + }); + }); + it('with 3 matching image to return the newest image', async () => { + ec2Mock.reset(); + ec2Mock.on(DescribeImagesCommand).resolves(mock3Images); + const res = await getPkgReleases({ + datasource, + depName: + '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with 3 matching image to return the newest image"]}]', + }); + expect(res).toStrictEqual({ + releases: [ + { + isDeprecated: false, + newDigest: image3.Name, + releaseTimestamp: image3.CreationDate, + version: image3.ImageId, + }, + ], + }); + }); + }); +}); diff --git a/lib/datasource/aws-machine-image/index.ts b/lib/datasource/aws-machine-image/index.ts new file mode 100644 index 00000000000000..4307b6154c27f4 --- /dev/null +++ b/lib/datasource/aws-machine-image/index.ts @@ -0,0 +1,114 @@ +import { DescribeImagesCommand, EC2Client, Image } from '@aws-sdk/client-ec2'; +import { cache } from '../../util/cache/package/decorator'; +import * as amazonMachineImageVersioning from '../../versioning/aws-machine-image'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, ReleaseResult } from '../types'; + +export class AwsMachineImageDataSource extends Datasource { + static readonly id = 'aws-machine-image'; + + override readonly defaultVersioning = amazonMachineImageVersioning.id; + + override readonly caching = true; + + override readonly defaultConfig = { + // Because AMIs don't follow any versioning scheme, we override commitMessageExtra to remove the 'v' + commitMessageExtra: 'to {{{newVersion}}}', + prBodyColumns: ['Change', 'Image'], + prBodyDefinitions: { + Image: '```{{{newDigest}}}```', + }, + digest: { + // Because newDigestShort will allways be 'amazon-' we override to print the name of the AMI + commitMessageExtra: 'to {{{newDigest}}}', + prBodyColumns: ['Image'], + prBodyDefinitions: { + Image: '```{{{newDigest}}}```', + }, + }, + }; + + private readonly ec2: EC2Client; + + private readonly now: number; + + constructor() { + super(AwsMachineImageDataSource.id); + this.ec2 = new EC2Client({}); + this.now = Date.now(); + } + + @cache({ + namespace: `datasource-${AwsMachineImageDataSource.id}`, + key: (serializedAmiFilter: string) => + `getSortedAwsMachineImages:${serializedAmiFilter}`, + }) + async getSortedAwsMachineImages( + serializedAmiFilter: string + ): Promise { + const cmd = new DescribeImagesCommand({ + Filters: JSON.parse(serializedAmiFilter), + }); + const matchingImages = await this.ec2.send(cmd); + matchingImages.Images = matchingImages.Images ?? []; + return matchingImages.Images.sort( + (image1, image2) => + Date.parse(image1.CreationDate) - Date.parse(image2.CreationDate) + ); + } + + @cache({ + namespace: `datasource-${AwsMachineImageDataSource.id}`, + key: ({ registryUrl, lookupName }: GetReleasesConfig, newValue: string) => + `getDigest:${registryUrl}:${lookupName}:${newValue ?? ''}`, + }) + override async getDigest( + { lookupName: serializedAmiFilter }: GetReleasesConfig, + newValue?: string + ): Promise { + const images = await this.getSortedAwsMachineImages(serializedAmiFilter); + if (images.length < 1) { + return null; + } + + if (newValue) { + const newValueMatchingImages = images.filter( + (image) => image.ImageId === newValue + ); + if (newValueMatchingImages.length === 1) { + return newValueMatchingImages[0].Name; + } + return null; + } + + return (await this.getReleases({ lookupName: serializedAmiFilter })) + .releases[0].newDigest; + } + + @cache({ + namespace: `datasource-${AwsMachineImageDataSource.id}`, + key: ({ registryUrl, lookupName }: GetReleasesConfig) => + `getReleases:${registryUrl}:${lookupName}`, + }) + async getReleases({ + lookupName: serializedAmiFilter, + }: GetReleasesConfig): Promise { + const images = await this.getSortedAwsMachineImages(serializedAmiFilter); + if (images.length === 0) { + return null; + } + const latestImage = images[images.length - 1]; + return { + releases: [ + { + version: latestImage.ImageId, + releaseTimestamp: latestImage.CreationDate, + isDeprecated: + Date.parse(latestImage.DeprecationTime ?? this.now.toString()) < + this.now, + newDigest: latestImage.Name, + }, + ], + }; + } +} diff --git a/lib/datasource/aws-machine-image/readme.md b/lib/datasource/aws-machine-image/readme.md new file mode 100644 index 00000000000000..e877248bb256e6 --- /dev/null +++ b/lib/datasource/aws-machine-image/readme.md @@ -0,0 +1,113 @@ +> :warning: **This datasource is _experimental_**: Be aware that its syntax and behavior may change at any time! + +This datasource returns the latest [Amazon Machine Image](https://docs.aws.amazon.com/en_en/AWSEC2/latest/UserGuide/AMIs.html) via the AWS API (valid credentials required). + +Because there is no general `lookupName`, you have to use the [describe images filter](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ec2/interfaces/describeimagescommandinput.html#filters) as minified JSON as a `lookupName`. + +Example: + +```yaml +# Getting the latest official EKS image from AWS (account '602401143452' for eu-central-1) for EKS 1.21 (name matches 'amazon-eks-node-1.21-*') would look as a describe images filter like: + +[ + { + "Name": "owner-id", + "Values": [ "602401143452" ] + }, + { + "Name": "name", + "Values": [ "amazon-eks-node-1.21-*" ] + } +] + +# In order to use it with this datasource, you have to minify it: + +[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.21-*"]}] +``` + +At the moment, this datasource has no "manager". +You have to use the regex manager for this. + +**Usage Example** + +Here's an example of using the regex manager: + +```javascript +module.exports = { + regexManagers: [ + { + fileMatch: ['.*'], + matchStrings: [ + '.*amiFilter=(?.*?)\\n(.*currentImageName=(?.*?)\\n)?(.*\\n)?.*?(?[a-zA-Z0-9-_:]*)[ ]*?[:|=][ ]*?["|\']?(?ami-[a-z0-9]{17})["|\']?.*', + ], + datasourceTemplate: 'aws-machine-image', + versioningTemplate: 'aws-machine-image', + }, + ], +}; +``` + +Or as JSON: + +```yaml +{ + 'regexManagers': + [ + { + 'fileMatch': ['.*'], + 'matchStrings': + [ + ".*amiFilter=(?.*?)\\n(.*currentImageName=(?.*?)\\n)?(.*\\n)?.*?(?[a-zA-Z0-9-_:]*)[ ]*?[:|=][ ]*?[\"|']?(?ami-[a-z0-9]{17})[\"|']?.*", + ], + 'datasourceTemplate': 'aws-machine-image', + 'versioningTemplate': 'aws-machine-image', + }, + ], +} +``` + +This would match every file, and would recognize the following lines: + +```yaml +# With AMI name mentioned in the comments +# amiFilter=[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.21-*"]}] +# currentImageName=unknown +my_ami1: ami-02ce3d9008cab69cb +# Only AMI, no name mentioned +# amiFilter=[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.20-*"]}] +# currentImageName=unknown +my_ami2: ami-0083e9407e275acf2 +``` + +```typescript +const myConfigObject = { + // With AMI name mentioned in the comments + // amiFilter=[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.21-*"]}] + // currentImageName=unknown + my_ami1: 'ami-02ce3d9008cab69cb', +}; + +/** + * Only AMI, no AMI name mentioned + * amiFilter=[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.20-*"]}] + * currentImageName=unknown + */ +const my_ami2 = 'ami-0083e9407e275acf2'; +``` + +```hcl +resource "aws_instance" "web" { + + # Only AMI, no name mentioned + # amiFilter=[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.20-*"]}] + # currentImageName=unknown + ami = "ami-0083e9407e275acf2" + + count = 2 + source_dest_check = false + + connection { + user = "root" + } +} +``` diff --git a/lib/datasource/bitbucket-tags/index.spec.ts b/lib/datasource/bitbucket-tags/index.spec.ts index ef011d6e13dac7..9e9e773b2897b6 100644 --- a/lib/datasource/bitbucket-tags/index.spec.ts +++ b/lib/datasource/bitbucket-tags/index.spec.ts @@ -73,7 +73,7 @@ describe('datasource/bitbucket-tags/index', () => { }); expect(res).toMatchSnapshot(); expect(res).toBeString(); - expect(res).toEqual('123'); + expect(res).toBe('123'); expect(httpMock.getTrace()).toMatchSnapshot(); }); }); @@ -123,7 +123,7 @@ describe('datasource/bitbucket-tags/index', () => { ); expect(res).toMatchSnapshot(); expect(res).toBeString(); - expect(res).toEqual('123'); + expect(res).toBe('123'); expect(httpMock.getTrace()).toMatchSnapshot(); }); }); diff --git a/lib/datasource/cdnjs/index.ts b/lib/datasource/cdnjs/index.ts index cac132885f6c67..8f3aa41dd2b51e 100644 --- a/lib/datasource/cdnjs/index.ts +++ b/lib/datasource/cdnjs/index.ts @@ -17,7 +17,7 @@ export class CdnJsDatasource extends Datasource { override readonly caching = true; // this.handleErrors will always throw - // eslint-disable-next-line consistent-return + async getReleases({ lookupName, registryUrl, @@ -25,6 +25,7 @@ export class CdnJsDatasource extends Datasource { // Each library contains multiple assets, so we cache at the library level instead of per-asset const library = lookupName.split('/')[0]; const url = `${registryUrl}libraries/${library}?fields=homepage,repository,assets`; + let result: ReleaseResult; try { const { assets, homepage, repository } = ( await this.http.getJson(url) @@ -37,7 +38,7 @@ export class CdnJsDatasource extends Datasource { .filter(({ files }) => files.includes(assetName)) .map(({ version, sri }) => ({ version, newDigest: sri[assetName] })); - const result: ReleaseResult = { releases }; + result = { releases }; if (homepage) { result.homepage = homepage; @@ -45,12 +46,12 @@ export class CdnJsDatasource extends Datasource { if (repository?.url) { result.sourceUrl = repository.url; } - return result; } catch (err) { if (err.statusCode !== 404) { throw new ExternalHostError(err); } this.handleGenericErrors(err); } + return result || null; } } diff --git a/lib/datasource/clojure/__snapshots__/index.spec.ts.snap b/lib/datasource/clojure/__snapshots__/index.spec.ts.snap index aed87d93324495..8be1ab42749a8d 100644 --- a/lib/datasource/clojure/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/clojure/__snapshots__/index.spec.ts.snap @@ -26,8 +26,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -71,8 +71,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -92,6 +92,15 @@ Array [ "method": "HEAD", "url": "https://clojars.org/repo/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "clojars.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -148,6 +157,9 @@ Object { "name": "package", "registryUrl": "https://clojars.org/repo", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -226,8 +238,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -271,8 +283,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -292,6 +304,15 @@ Array [ "method": "HEAD", "url": "https://clojars.org/repo/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "clojars.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -339,8 +360,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -384,8 +405,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -405,6 +426,15 @@ Array [ "method": "HEAD", "url": "https://clojars.org/repo/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "clojars.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -447,8 +477,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -492,8 +522,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -513,6 +543,15 @@ Array [ "method": "HEAD", "url": "https://clojars.org/repo/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "clojars.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -536,6 +575,9 @@ Array [ exports[`datasource/clojure/index ignores unsupported protocols 1`] = ` Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -577,8 +619,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "http://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "http://clojars.org/repo/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -622,8 +664,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "http://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "http://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -643,6 +685,15 @@ Array [ "method": "HEAD", "url": "http://clojars.org/repo/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "clojars.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "http://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -672,6 +723,9 @@ Object { "name": "package", "registryUrl": "https://custom.registry.renovatebot.com", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -714,8 +768,8 @@ Array [ "host": "custom.registry.renovatebot.com", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://custom.registry.renovatebot.com/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -759,8 +813,8 @@ Array [ "host": "custom.registry.renovatebot.com", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -780,6 +834,15 @@ Array [ "method": "HEAD", "url": "https://custom.registry.renovatebot.com/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "custom.registry.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -809,6 +872,9 @@ Object { "name": "package", "registryUrl": "https://clojars.org/repo", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -860,8 +926,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -905,8 +971,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -926,6 +992,15 @@ Array [ "method": "HEAD", "url": "https://clojars.org/repo/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "clojars.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -955,6 +1030,9 @@ Object { "name": "package", "registryUrl": "https://clojars.org/repo", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -1006,8 +1084,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -1051,8 +1129,8 @@ Array [ "host": "clojars.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -1072,6 +1150,15 @@ Array [ "method": "HEAD", "url": "https://clojars.org/repo/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "clojars.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://clojars.org/repo/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", diff --git a/lib/datasource/clojure/index.spec.ts b/lib/datasource/clojure/index.spec.ts index 642829eb34ae8a..5cd42ec50797fa 100644 --- a/lib/datasource/clojure/index.spec.ts +++ b/lib/datasource/clojure/index.spec.ts @@ -42,6 +42,7 @@ function mockGenericPackage(opts: MockOpts = {}) { const jars = opts.jars === undefined ? { + '0.0.1': 200, '1.0.0': 200, '1.0.1': 404, '1.0.2': 500, @@ -94,9 +95,12 @@ function mockGenericPackage(opts: MockOpts = {}) { .map((x) => parseInt(x, 10)) .map((x) => (x < 10 ? `0${x}` : `${x}`)); const timestamp = `2020-01-01T${major}:${minor}:${patch}.000Z`; + const headers = version.startsWith('0.') + ? {} + : { 'Last-Modified': timestamp }; scope .head(`/${packagePath}/${version}/${artifact}-${version}.pom`) - .reply(status, '', { 'Last-Modified': timestamp }); + .reply(status, '', headers); }); } @@ -188,6 +192,7 @@ describe('datasource/clojure/index', () => { ); expect(releases).toMatchObject([ + { version: '0.0.1' }, { version: '1.0.0' }, { version: '1.0.3-SNAPSHOT' }, { version: '2.0.0' }, @@ -294,7 +299,7 @@ describe('datasource/clojure/index', () => { it('returns null for invalid registryUrls', async () => { const res = await get( 'org.example:package', - // eslint-disable-next-line no-template-curly-in-string + '${project.baseUri}../../repository/' ); expect(res).toBeNull(); @@ -311,6 +316,6 @@ describe('datasource/clojure/index', () => { const { sourceUrl } = await get(); - expect(sourceUrl).toEqual('https://github.com/example/test'); + expect(sourceUrl).toBe('https://github.com/example/test'); }); }); diff --git a/lib/datasource/clojure/index.ts b/lib/datasource/clojure/index.ts index 79b2c8cf034a46..513909ab9a8598 100644 --- a/lib/datasource/clojure/index.ts +++ b/lib/datasource/clojure/index.ts @@ -17,7 +17,6 @@ export class ClojureDatasource extends Datasource { MAVEN_REPO, ]; - // eslint-disable-next-line class-methods-use-this getReleases({ lookupName, registryUrl, diff --git a/lib/datasource/crate/index.spec.ts b/lib/datasource/crate/index.spec.ts index 1e6a0f9b599a1a..37fbfdde9518e8 100644 --- a/lib/datasource/crate/index.spec.ts +++ b/lib/datasource/crate/index.spec.ts @@ -6,7 +6,7 @@ import { dirname, join } from 'upath'; import { getPkgReleases } from '..'; import * as httpMock from '../../../test/http-mock'; import { loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as memCache from '../../util/cache/memory'; import { RegistryFlavor, RegistryInfo } from './types'; @@ -102,7 +102,7 @@ describe('datasource/crate/index', () => { localDir: join(tmpDir.path, 'local'), cacheDir: join(tmpDir.path, 'cache'), }; - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); simpleGit.mockReset(); memCache.init(); @@ -111,7 +111,7 @@ describe('datasource/crate/index', () => { afterEach(async () => { await tmpDir.cleanup(); tmpDir = null; - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null for missing registry url', async () => { @@ -247,7 +247,7 @@ describe('datasource/crate/index', () => { }); it('clones cloudsmith private registry', async () => { const { mockClone } = setupGitMocks(); - setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true }); + GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); const url = 'https://dl.cloudsmith.io/basic/myorg/myrepo/cargo/index.git'; const res = await getPkgReleases({ datasource, @@ -261,7 +261,7 @@ describe('datasource/crate/index', () => { }); it('clones other private registry', async () => { const { mockClone } = setupGitMocks(); - setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true }); + GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); const url = 'https://github.com/mcorbin/testregistry'; const res = await getPkgReleases({ datasource, @@ -275,7 +275,7 @@ describe('datasource/crate/index', () => { }); it('clones once then reuses the cache', async () => { const { mockClone } = setupGitMocks(); - setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true }); + GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); const url = 'https://github.com/mcorbin/othertestregistry'; await getPkgReleases({ datasource, @@ -291,7 +291,7 @@ describe('datasource/crate/index', () => { }); it('guards against race conditions while cloning', async () => { const { mockClone } = setupGitMocks(250); - setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true }); + GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); const url = 'https://github.com/mcorbin/othertestregistry'; await Promise.all([ @@ -317,7 +317,7 @@ describe('datasource/crate/index', () => { }); it('returns null when git clone fails', async () => { setupErrorGitMock(); - setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true }); + GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); const url = 'https://github.com/mcorbin/othertestregistry'; const result = await getPkgReleases({ diff --git a/lib/datasource/crate/index.ts b/lib/datasource/crate/index.ts index 9eed05c056f34d..acc4841adf1161 100644 --- a/lib/datasource/crate/index.ts +++ b/lib/datasource/crate/index.ts @@ -1,12 +1,13 @@ import hasha from 'hasha'; import Git from 'simple-git'; import { join } from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import * as memCache from '../../util/cache/memory'; import { cache } from '../../util/cache/package/decorator'; import { privateCacheDir, readFile } from '../../util/fs'; import { simpleGitConfig } from '../../util/git/config'; +import { regEx } from '../../util/regex'; import * as cargoVersioning from '../../versioning/cargo'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; @@ -143,7 +144,7 @@ export class CrateDatasource extends Datasource { * clone the repository. */ private static cacheDirFromUrl(url: URL): string { - const proto = url.protocol.replace(/:$/, ''); // TODO #12070 + const proto = url.protocol.replace(regEx(/:$/), ''); const host = url.hostname; const hash = hasha(url.pathname, { algorithm: 'sha256', @@ -186,7 +187,7 @@ export class CrateDatasource extends Datasource { }; if (flavor !== RegistryFlavor.CratesIo) { - if (!getGlobalConfig().allowCustomCrateRegistries) { + if (!GlobalConfig.get('allowCustomCrateRegistries')) { logger.warn( 'crate datasource: allowCustomCrateRegistries=true is required for registries other than crates.io, bailing out' ); @@ -216,7 +217,7 @@ export class CrateDatasource extends Datasource { `Cloning private cargo registry` ); - const git = Git(simpleGitConfig()); + const git = Git({ ...simpleGitConfig(), maxConcurrentProcesses: 1 }); const clonePromise = git.clone(registryUrl, clonePath, { '--depth': 1, }); diff --git a/lib/datasource/datasource.ts b/lib/datasource/datasource.ts index 5b2a2f68e02a17..84b21952797f48 100644 --- a/lib/datasource/datasource.ts +++ b/lib/datasource/datasource.ts @@ -33,7 +33,7 @@ export abstract class Datasource implements DatasourceApi { getDigest?(config: DigestConfig, newValue?: string): Promise; - // eslint-disable-next-line class-methods-use-this + // eslint-disable-next-line @typescript-eslint/no-empty-function handleSpecificErrors(err: HttpError): void {} protected handleGenericErrors(err: HttpError): never { diff --git a/lib/datasource/docker/readme.md b/lib/datasource/docker/readme.md new file mode 100644 index 00000000000000..fac24ca3072660 --- /dev/null +++ b/lib/datasource/docker/readme.md @@ -0,0 +1,5 @@ +This datasource identifies an image's source repository according to the [pre-defined annotation keys of the OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md). + +This datasource looks for the metadata of the **latest stable** image found on the Docker registry and uses the value of the label `org.opencontainers.image.source` as the `sourceUrl`. + +The [Label Schema](https://label-schema.org/) is superseded by OCI annotations, therefore this datasource does not support the `org.label-schema.vcs-url` label. diff --git a/lib/datasource/docker/types.ts b/lib/datasource/docker/types.ts index 72f45fd37ec7fc..a73f044b961609 100644 --- a/lib/datasource/docker/types.ts +++ b/lib/datasource/docker/types.ts @@ -1,3 +1,5 @@ +// FIXME #12556 +/* eslint-disable @typescript-eslint/naming-convention */ /** * Media Types * https://docs.docker.com/registry/spec/manifest-v2-2/#media-types @@ -7,6 +9,7 @@ export enum MediaType { manifestV2 = 'application/vnd.docker.distribution.manifest.v2+json', manifestListV2 = 'application/vnd.docker.distribution.manifest.list.v2+json', } +/* eslint-enable @typescript-eslint/naming-convention */ export interface MediaObject { readonly digest: string; diff --git a/lib/datasource/galaxy-collection/index.ts b/lib/datasource/galaxy-collection/index.ts index 8b57b2063eec68..74c333c9519146 100644 --- a/lib/datasource/galaxy-collection/index.ts +++ b/lib/datasource/galaxy-collection/index.ts @@ -109,7 +109,7 @@ export class GalaxyCollectionDatasource extends Datasource { { concurrency: 5 } // allow 5 requests at maximum in parallel ); // filter failed versions - const filteredReleases = enrichedReleases.filter((value) => value != null); + const filteredReleases = enrichedReleases.filter(Boolean); // extract base information which are only provided on the release from the newest release const result: ReleaseResult = { releases: filteredReleases, diff --git a/lib/datasource/git-refs/__fixtures__/ls-remote-1.txt b/lib/datasource/git-refs/__fixtures__/ls-remote-1.txt index 75a2d317e8ad4d..25b48574669fa0 100644 --- a/lib/datasource/git-refs/__fixtures__/ls-remote-1.txt +++ b/lib/datasource/git-refs/__fixtures__/ls-remote-1.txt @@ -1,4 +1,5 @@ a9920c014aebc28dc1b23e7efcc006d0455cc710 HEAD +46fd703d4738905cd55e1c5c36a70e5d43432b9c refs/for/master 2e24e927538bbc03cc3cd946834c8f5fe333f32c refs/heads/feat/slim-image a9920c014aebc28dc1b23e7efcc006d0455cc710 refs/heads/master a9920c014aebc28dc1b23e7efcc006d045512345 refs/heads/v1.0.0 diff --git a/lib/datasource/git-refs/index.spec.ts b/lib/datasource/git-refs/index.spec.ts index 56adb9d9a1c7e5..dd626c71835eba 100644 --- a/lib/datasource/git-refs/index.spec.ts +++ b/lib/datasource/git-refs/index.spec.ts @@ -91,6 +91,18 @@ describe('datasource/git-refs/index', () => { ); expect(digest).toMatchSnapshot(); }); + it('ignores refs/for/', async () => { + simpleGit.mockReturnValue({ + listRemote() { + return Promise.resolve(lsRemote1); + }, + }); + const digest = await new GitRefsDatasource().getDigest( + { lookupName: 'a tag to look up' }, + 'master' + ); + expect(digest).toBe('a9920c014aebc28dc1b23e7efcc006d0455cc710'); + }); it('returns digest for HEAD', async () => { simpleGit.mockReturnValue({ listRemote() { diff --git a/lib/datasource/git-refs/index.ts b/lib/datasource/git-refs/index.ts index dcd2e603506bfc..81e14e933b74ef 100644 --- a/lib/datasource/git-refs/index.ts +++ b/lib/datasource/git-refs/index.ts @@ -22,7 +22,6 @@ export class GitRefsDatasource extends Datasource { namespace: `datasource-${GitRefsDatasource.id}`, key: ({ lookupName }: GetReleasesConfig) => lookupName, }) - // eslint-disable-next-line class-methods-use-this override async getReleases({ lookupName, }: GetReleasesConfig): Promise { @@ -54,7 +53,6 @@ export class GitRefsDatasource extends Datasource { return result; } - // eslint-disable-next-line class-methods-use-this override async getDigest( { lookupName }: DigestConfig, newValue?: string @@ -63,8 +61,17 @@ export class GitRefsDatasource extends Datasource { { lookupName }, this.id ); - const findValue = newValue || 'HEAD'; - const ref = rawRefs.find((rawRef) => rawRef.value === findValue); + let ref: RawRefs; + if (newValue) { + ref = rawRefs.find( + (rawRef) => + ['heads', 'tags'].includes(rawRef.type) && rawRef.value === newValue + ); + } else { + ref = rawRefs.find( + (rawRef) => rawRef.type === '' && rawRef.value === 'HEAD' + ); + } if (ref) { return ref.hash; } diff --git a/lib/datasource/git-tags/index.ts b/lib/datasource/git-tags/index.ts index f48a66fc10b9a4..e155a4d2e61bfb 100644 --- a/lib/datasource/git-tags/index.ts +++ b/lib/datasource/git-tags/index.ts @@ -18,7 +18,6 @@ export class GitTagsDatasource extends Datasource { namespace: `datasource-${GitTagsDatasource.id}`, key: ({ lookupName }: GetReleasesConfig) => lookupName, }) - // eslint-disable-next-line class-methods-use-this async getReleases({ lookupName, }: GetReleasesConfig): Promise { @@ -48,7 +47,6 @@ export class GitTagsDatasource extends Datasource { return result; } - // eslint-disable-next-line class-methods-use-this override async getDigest( { lookupName }: DigestConfig, newValue?: string diff --git a/lib/datasource/github-releases/index.spec.ts b/lib/datasource/github-releases/index.spec.ts index 99f4a5381ebfa6..52b5d1189658b3 100644 --- a/lib/datasource/github-releases/index.spec.ts +++ b/lib/datasource/github-releases/index.spec.ts @@ -15,7 +15,8 @@ const responseBody = [ { tag_name: 'a', published_at: '2020-03-09T13:00:00Z' }, { tag_name: 'v', published_at: '2020-03-09T12:00:00Z' }, { tag_name: '1.0.0', published_at: '2020-03-09T11:00:00Z' }, - { tag_name: 'v1.1.0', published_at: '2020-03-09T10:00:00Z' }, + { tag_name: 'v1.1.0', draft: false, published_at: '2020-03-09T10:00:00Z' }, + { tag_name: '1.2.0', draft: true, published_at: '2020-03-09T10:00:00Z' }, { tag_name: '2.0.0', published_at: '2020-04-09T10:00:00Z', @@ -47,6 +48,9 @@ describe('datasource/github-releases/index', () => { expect( res.releases.find((release) => release.version === 'v1.1.0') ).toBeDefined(); + expect( + res.releases.find((release) => release.version === '1.2.0') + ).toBeUndefined(); expect( res.releases.find((release) => release.version === '2.0.0').isStable ).toBeFalse(); diff --git a/lib/datasource/github-releases/index.ts b/lib/datasource/github-releases/index.ts index c7db64bd1a9e93..1c58489a9f51d4 100644 --- a/lib/datasource/github-releases/index.ts +++ b/lib/datasource/github-releases/index.ts @@ -55,14 +55,14 @@ export async function getReleases({ sourceUrl: getSourceUrl(repo, registryUrl), releases: null, }; - dependency.releases = githubReleases.map( - ({ tag_name, published_at, prerelease }) => ({ + dependency.releases = githubReleases + .filter(({ draft }) => draft !== true) + .map(({ tag_name, published_at, prerelease }) => ({ version: tag_name, gitRef: tag_name, releaseTimestamp: published_at, isStable: prerelease ? false : undefined, - }) - ); + })); const cacheMinutes = 10; await packageCache.set(cacheNamespace, cacheKey, dependency, cacheMinutes); return dependency; diff --git a/lib/datasource/github-releases/types.ts b/lib/datasource/github-releases/types.ts index 2989ddcaee72fc..36f79f6922a104 100644 --- a/lib/datasource/github-releases/types.ts +++ b/lib/datasource/github-releases/types.ts @@ -3,6 +3,7 @@ export type GithubRelease = { tag_name: string; published_at: string; prerelease: boolean; + draft?: boolean; assets: GithubReleaseAsset[]; html_url: string; diff --git a/lib/datasource/go/digest.spec.ts b/lib/datasource/go/digest.spec.ts index 0da89888e90f36..1439970baa663f 100644 --- a/lib/datasource/go/digest.spec.ts +++ b/lib/datasource/go/digest.spec.ts @@ -1,7 +1,7 @@ import * as httpMock from '../../../test/http-mock'; import { mocked } from '../../../test/util'; import * as _hostRules from '../../util/host-rules'; -import { getDigest } from './digest'; +import { getDigest } from '.'; jest.mock('../../util/host-rules'); diff --git a/lib/datasource/go/index.ts b/lib/datasource/go/index.ts index dcc924c25ff9ca..2e805c7197cd5f 100644 --- a/lib/datasource/go/index.ts +++ b/lib/datasource/go/index.ts @@ -4,6 +4,8 @@ import * as goproxy from './releases-goproxy'; export { id } from './common'; +export { getDigest } from './digest'; + export const customRegistrySupport = false; export function getReleases( diff --git a/lib/datasource/go/releases-goproxy.spec.ts b/lib/datasource/go/releases-goproxy.spec.ts index 4d85ca647bdf9b..e6ec16ca38612d 100644 --- a/lib/datasource/go/releases-goproxy.spec.ts +++ b/lib/datasource/go/releases-goproxy.spec.ts @@ -98,15 +98,15 @@ describe('datasource/go/releases-goproxy', () => { expect(parseNoproxy(undefined)).toBeNull(); expect(parseNoproxy(null)).toBeNull(); expect(parseNoproxy('')).toBeNull(); - expect(parseNoproxy('*')?.source).toEqual('^(?:[^\\/]*)$'); - expect(parseNoproxy('?')?.source).toEqual('^(?:[^\\/])$'); - expect(parseNoproxy('foo')?.source).toEqual('^(?:foo)$'); - expect(parseNoproxy('\\f\\o\\o')?.source).toEqual('^(?:foo)$'); - expect(parseNoproxy('foo,bar')?.source).toEqual('^(?:foo|bar)$'); - expect(parseNoproxy('[abc]')?.source).toEqual('^(?:[abc])$'); - expect(parseNoproxy('[a-c]')?.source).toEqual('^(?:[a-c])$'); - expect(parseNoproxy('[\\a-\\c]')?.source).toEqual('^(?:[a-c])$'); - expect(parseNoproxy('a.b.c')?.source).toEqual('^(?:a\\.b\\.c)$'); + expect(parseNoproxy('*')?.source).toBe('^(?:[^\\/]*)$'); + expect(parseNoproxy('?')?.source).toBe('^(?:[^\\/])$'); + expect(parseNoproxy('foo')?.source).toBe('^(?:foo)$'); + expect(parseNoproxy('\\f\\o\\o')?.source).toBe('^(?:foo)$'); + expect(parseNoproxy('foo,bar')?.source).toBe('^(?:foo|bar)$'); + expect(parseNoproxy('[abc]')?.source).toBe('^(?:[abc])$'); + expect(parseNoproxy('[a-c]')?.source).toBe('^(?:[a-c])$'); + expect(parseNoproxy('[\\a-\\c]')?.source).toBe('^(?:[a-c])$'); + expect(parseNoproxy('a.b.c')?.source).toBe('^(?:a\\.b\\.c)$'); }); it('matches on real package prefixes', () => { diff --git a/lib/datasource/go/releases-goproxy.ts b/lib/datasource/go/releases-goproxy.ts index 5acb7a080b5b0a..c35de8224feea6 100644 --- a/lib/datasource/go/releases-goproxy.ts +++ b/lib/datasource/go/releases-goproxy.ts @@ -37,9 +37,9 @@ export function parseGoproxy( } const result: GoproxyItem[] = input - .split(/([^,|]*(?:,|\|))/) // TODO: #12070 + .split(regEx(/([^,|]*(?:,|\|))/)) .filter(Boolean) - .map((s) => s.split(/(?=,|\|)/)) // TODO: #12070 + .map((s) => s.split(/(?=,|\|)/)) // TODO: #12872 lookahead .map(([url, separator]) => ({ url, fallback: @@ -56,7 +56,7 @@ export function parseGoproxy( const lexer = moo.states({ main: { separator: { - match: /\s*?,\s*?/, // TODO #12070 + match: /\s*?,\s*?/, // TODO #12870 value: (_: string) => '|', }, asterisk: { @@ -77,14 +77,14 @@ const lexer = moo.states({ value: (s: string) => s.replace(regEx('\\.', 'g'), '\\.'), }, escapedChar: { - match: /\\./, // TODO #12070 + match: /\\./, // TODO #12870 value: (s: string) => s.slice(1), }, }, characterRange: { - char: /[^\\\]\n]/, // TODO #12070 + char: /[^\\\]\n]/, // TODO #12870 escapedChar: { - match: /\\./, // TODO #12070 + match: /\\./, // TODO #12870 value: (s: string) => s.slice(1), }, characterRangeEnd: { diff --git a/lib/datasource/helm/index.spec.ts b/lib/datasource/helm/index.spec.ts index 051b5df29f66b0..31544ce46e46a3 100644 --- a/lib/datasource/helm/index.spec.ts +++ b/lib/datasource/helm/index.spec.ts @@ -180,7 +180,7 @@ describe('datasource/helm/index', () => { registryUrls: ['https://example-repository.com/subdir'], }); const trace = httpMock.getTrace(); - expect(trace[0].url).toEqual( + expect(trace[0].url).toBe( 'https://example-repository.com/subdir/index.yaml' ); expect(trace).toMatchSnapshot(); diff --git a/lib/datasource/index.spec.ts b/lib/datasource/index.spec.ts index 95a2a1b1e18db2..b26cf8d7e16867 100644 --- a/lib/datasource/index.spec.ts +++ b/lib/datasource/index.spec.ts @@ -137,7 +137,7 @@ describe('datasource/index', () => { versioning: 'loose', }); expect(res.releases).toHaveLength(1); - expect(res.releases[0].version).toEqual('v1.0'); + expect(res.releases[0].version).toBe('v1.0'); }); it('adds sourceUrl', async () => { npmDatasource.getReleases.mockResolvedValue({ @@ -285,7 +285,7 @@ describe('datasource/index', () => { datasource: datasourceNpm.id, depName: 'abc', }); - expect(res.sourceUrl).toEqual('https://abc.com'); + expect(res.sourceUrl).toBe('https://abc.com'); }); it('massages sourceUrl', async () => { npmDatasource.getReleases.mockResolvedValue({ @@ -296,6 +296,20 @@ describe('datasource/index', () => { datasource: datasourceNpm.id, depName: 'cas', }); - expect(res.sourceUrl).toEqual('https://github.com/Jasig/cas'); + expect(res.sourceUrl).toBe('https://github.com/Jasig/cas'); + }); + + it('applies replacements', async () => { + npmDatasource.getReleases.mockResolvedValue({ + releases: [{ version: '1.0.0' }], + }); + const res = await datasource.getPkgReleases({ + datasource: datasourceNpm.id, + depName: 'abc', + replacementName: 'def', + replacementVersion: '2.0.0', + }); + expect(res.replacementName).toBe('def'); + expect(res.replacementVersion).toBe('2.0.0'); }); }); diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts index 7e1adab369a674..d7ec46c4a142f9 100644 --- a/lib/datasource/index.ts +++ b/lib/datasource/index.ts @@ -63,7 +63,7 @@ async function getRegistryReleases( ); // istanbul ignore if if (cachedResult) { - logger.debug({ cacheKey }, 'Returning cached datasource response'); + logger.trace({ cacheKey }, 'Returning cached datasource response'); return cachedResult; } } @@ -213,6 +213,18 @@ export function getDefaultVersioning(datasourceName: string): string { return datasource?.defaultVersioning || 'semver'; } +function applyReplacements( + config: GetReleasesInternalConfig +): Pick | undefined { + if (config.replacementName && config.replacementVersion) { + return { + replacementName: config.replacementName, + replacementVersion: config.replacementVersion, + }; + } + return undefined; +} + async function fetchReleases( config: GetReleasesInternalConfig ): Promise { @@ -250,6 +262,7 @@ async function fetchReleases( return null; } addMetaData(dep, datasourceName, config.lookupName); + dep = { ...dep, ...applyReplacements(config) }; return dep; } @@ -354,7 +367,7 @@ export async function getPkgReleases( } // Strip constraints from releases result res.releases.forEach((release) => { - delete release.constraints; // eslint-disable-line no-param-reassign + delete release.constraints; }); return res; } diff --git a/lib/datasource/jenkins-plugins/common.ts b/lib/datasource/jenkins-plugins/common.ts deleted file mode 100644 index 22acbbb5ab896d..00000000000000 --- a/lib/datasource/jenkins-plugins/common.ts +++ /dev/null @@ -1 +0,0 @@ -export const id = 'jenkins-plugins'; diff --git a/lib/datasource/jenkins-plugins/get.ts b/lib/datasource/jenkins-plugins/get.ts deleted file mode 100644 index 14b517d6a8928f..00000000000000 --- a/lib/datasource/jenkins-plugins/get.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { logger } from '../../logger'; -import { ExternalHostError } from '../../types/errors/external-host-error'; -import { clone } from '../../util/clone'; -import { getElapsedMinutes } from '../../util/date'; -import { Http } from '../../util/http'; -import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; -import { id } from './common'; -import type { - JenkinsCache, - JenkinsCacheTypes, - JenkinsPluginsInfoResponse, - JenkinsPluginsVersionsResponse, -} from './types'; - -const http = new Http(id); - -const packageInfoUrl = - 'https://updates.jenkins.io/current/update-center.actual.json'; -const packageVersionsUrl = - 'https://updates.jenkins.io/current/plugin-versions.json'; - -function hasCacheExpired(cache: JenkinsCache): boolean { - return getElapsedMinutes(cache.lastSync) >= cache.cacheTimeMin; -} - -async function updateJenkinsCache( - cache: JenkinsCache, - updateHandler: () => Promise -): Promise { - if (hasCacheExpired(cache)) { - // eslint-disable-next-line no-param-reassign - cache.updatePromise = - // eslint-disable-next-line @typescript-eslint/no-misused-promises - cache.updatePromise || updateHandler(); - await cache.updatePromise; - - cache.updatePromise = null; // eslint-disable-line no-param-reassign - } -} - -function updateJenkinsPluginInfoCacheCallback( - response: JenkinsPluginsInfoResponse, - cache: JenkinsCache -): void { - for (const name of Object.keys(response.plugins || [])) { - // eslint-disable-next-line no-param-reassign - cache.cache[name] = { - releases: [], // releases are stored in another cache - sourceUrl: response.plugins[name]?.scm, - }; - } -} - -function updateJenkinsPluginVersionsCacheCallback( - response: JenkinsPluginsVersionsResponse, - cache: JenkinsCache -): void { - const plugins = response.plugins; - for (const name of Object.keys(plugins || [])) { - // eslint-disable-next-line no-param-reassign - cache.cache[name] = Object.keys(plugins[name]).map((version) => ({ - version, - downloadUrl: plugins[name][version]?.url, - releaseTimestamp: plugins[name][version]?.buildDate - ? new Date(plugins[name][version].buildDate + ' UTC') - : null, - })); - } -} - -async function getJenkinsUpdateCenterResponse( - cache: JenkinsCache -): Promise { - let response: T; - - const options = { - headers: { - 'Accept-Encoding': 'gzip, deflate, br', - }, - }; - - try { - logger.debug(`jenkins-plugins: Fetching Jenkins plugins ${cache.name}`); - const startTime = Date.now(); - response = (await http.getJson(cache.dataUrl, options)).body; - const durationMs = Math.round(Date.now() - startTime); - logger.debug( - { durationMs }, - `jenkins-plugins: Fetched Jenkins plugins ${cache.name}` - ); - } catch (err) /* istanbul ignore next */ { - // eslint-disable-next-line no-param-reassign - cache.cache = Object.create(null); - throw new ExternalHostError( - new Error(`jenkins-plugins: Fetch plugins ${cache.name} error`) - ); - } - - return response; -} - -async function updateJenkinsPluginCache( - cache: JenkinsCache, - // eslint-disable-next-line @typescript-eslint/no-shadow - callback: (resp: T, cache: JenkinsCache) => void -): Promise { - const response = await getJenkinsUpdateCenterResponse(cache); - if (response) { - callback(response, cache); - } - cache.lastSync = new Date(); // eslint-disable-line no-param-reassign -} - -const pluginInfoCache: JenkinsCache = { - name: 'info', - dataUrl: packageInfoUrl, - lastSync: new Date('2000-01-01'), - cacheTimeMin: 1440, - cache: Object.create(null), -}; - -const pluginVersionsCache: JenkinsCache = { - name: 'versions', - dataUrl: packageVersionsUrl, - lastSync: new Date('2000-01-01'), - cacheTimeMin: 60, - cache: Object.create(null), -}; - -async function updateJenkinsPluginInfoCache(): Promise { - await updateJenkinsPluginCache( - pluginInfoCache, - updateJenkinsPluginInfoCacheCallback - ); -} - -async function updateJenkinsPluginVersionsCache(): Promise { - await updateJenkinsPluginCache( - pluginVersionsCache, - updateJenkinsPluginVersionsCacheCallback - ); -} - -export async function getJenkinsPluginDependency( - lookupName: string -): Promise { - logger.debug(`getJenkinsDependency(${lookupName})`); - await updateJenkinsCache(pluginInfoCache, updateJenkinsPluginInfoCache); - await updateJenkinsCache( - pluginVersionsCache, - updateJenkinsPluginVersionsCache - ); - - const plugin = pluginInfoCache.cache[lookupName]; - if (!plugin) { - return null; - } - - const result = clone(plugin); - const releases = pluginVersionsCache.cache[lookupName]; - result.releases = releases ? clone(releases) : []; - return result; -} - -export function getReleases({ - lookupName, -}: GetReleasesConfig): Promise { - return getJenkinsPluginDependency(lookupName); -} - -function resetJenkinsCache(cache: JenkinsCache): void { - // eslint-disable-next-line no-param-reassign - cache.lastSync = new Date('2000-01-01'); - cache.cache = Object.create(null); // eslint-disable-line no-param-reassign -} - -// Note: use only for tests -export function resetCache(): void { - resetJenkinsCache(pluginInfoCache); - resetJenkinsCache(pluginVersionsCache); -} diff --git a/lib/datasource/jenkins-plugins/index.spec.ts b/lib/datasource/jenkins-plugins/index.spec.ts index 9057c644389015..429bc6d4402282 100644 --- a/lib/datasource/jenkins-plugins/index.spec.ts +++ b/lib/datasource/jenkins-plugins/index.spec.ts @@ -2,34 +2,24 @@ import { getPkgReleases } from '..'; import * as httpMock from '../../../test/http-mock'; import { loadJsonFixture } from '../../../test/util'; import * as versioning from '../../versioning/docker'; -import { resetCache } from './get'; -import * as jenkins from '.'; +import { JenkinsPluginsDatasource } from '.'; const jenkinsPluginsVersions = loadJsonFixture('plugin-versions.json'); const jenkinsPluginsInfo = loadJsonFixture('update-center.actual.json'); describe('datasource/jenkins-plugins/index', () => { describe('getReleases', () => { - const SKIP_CACHE = process.env.RENOVATE_SKIP_CACHE; - const params = { versioning: versioning.id, - datasource: jenkins.id, + datasource: JenkinsPluginsDatasource.id, depName: 'email-ext', registryUrls: ['https://updates.jenkins.io/'], }; - beforeEach(() => { - resetCache(); - process.env.RENOVATE_SKIP_CACHE = 'true'; - jest.resetAllMocks(); - }); - afterEach(() => { if (!httpMock.allUsed()) { throw new Error('Not all http mocks have been used!'); } - process.env.RENOVATE_SKIP_CACHE = SKIP_CACHE; }); it('returns null for a package miss', async () => { @@ -41,11 +31,6 @@ describe('datasource/jenkins-plugins/index', () => { .get('/current/update-center.actual.json') .reply(200, jenkinsPluginsInfo); - httpMock - .scope('https://updates.jenkins.io') - .get('/current/plugin-versions.json') - .reply(200, jenkinsPluginsVersions); - expect(await getPkgReleases(newparams)).toBeNull(); }); @@ -60,7 +45,7 @@ describe('datasource/jenkins-plugins/index', () => { .get('/current/plugin-versions.json') .reply(200, jenkinsPluginsVersions); - let res = await getPkgReleases(params); + const res = await getPkgReleases(params); expect(res.releases).toHaveLength(75); expect(res).toMatchSnapshot(); @@ -74,10 +59,6 @@ describe('datasource/jenkins-plugins/index', () => { expect( res.releases.find((release) => release.version === '12.98') ).toBeUndefined(); - - // check that caching is working and no http requests are done after the first call to getPkgReleases - res = await getPkgReleases(params); - expect(res.releases).toHaveLength(75); }); it('returns package releases for a hit for info and miss for releases', async () => { @@ -106,11 +87,6 @@ describe('datasource/jenkins-plugins/index', () => { .get('/current/update-center.actual.json') .reply(200, '{}'); - httpMock - .scope('https://updates.jenkins.io') - .get('/current/plugin-versions.json') - .reply(200, '{}'); - expect(await getPkgReleases(params)).toBeNull(); }); }); diff --git a/lib/datasource/jenkins-plugins/index.ts b/lib/datasource/jenkins-plugins/index.ts index 5790e0e57ca2b4..dfafe85cb833b8 100644 --- a/lib/datasource/jenkins-plugins/index.ts +++ b/lib/datasource/jenkins-plugins/index.ts @@ -1,5 +1,102 @@ -export { id } from './common'; -export { getReleases } from './get'; -export const customRegistrySupport = true; -export const defaultRegistryUrls = ['https://updates.jenkins.io']; -export const registryStrategy = 'hunt'; +import { logger } from '../../logger'; +import { cache } from '../../util/cache/package/decorator'; +import { clone } from '../../util/clone'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; +import type { + JenkinsPluginsInfoResponse, + JenkinsPluginsVersionsResponse, +} from './types'; + +export class JenkinsPluginsDatasource extends Datasource { + static readonly id = 'jenkins-plugins'; + + constructor() { + super(JenkinsPluginsDatasource.id); + } + + override readonly defaultRegistryUrls = ['https://updates.jenkins.io']; + + override readonly registryStrategy = 'hunt'; + + private static readonly packageInfoUrl = + 'https://updates.jenkins.io/current/update-center.actual.json'; + private static readonly packageVersionsUrl = + 'https://updates.jenkins.io/current/plugin-versions.json'; + + async getReleases({ + lookupName, + }: GetReleasesConfig): Promise { + const plugins = await this.getJenkinsPluginInfo(); + const plugin = plugins[lookupName]; + if (!plugin) { + return null; + } + + const result = clone(plugin); + const versions = await this.getJenkinsPluginVersions(); + const releases = versions[lookupName]; + result.releases = releases ? clone(releases) : []; + return result; + } + + @cache({ + namespace: JenkinsPluginsDatasource.id, + key: 'info', + ttlMinutes: 1440, + }) + async getJenkinsPluginInfo(): Promise> { + const { plugins } = + await this.getJenkinsUpdateCenterResponse( + JenkinsPluginsDatasource.packageInfoUrl + ); + + const info: Record = {}; + for (const name of Object.keys(plugins ?? [])) { + info[name] = { + releases: [], // releases + sourceUrl: plugins[name]?.scm, + }; + } + return info; + } + + @cache({ namespace: JenkinsPluginsDatasource.id, key: 'versions' }) + async getJenkinsPluginVersions(): Promise> { + const { plugins } = + await this.getJenkinsUpdateCenterResponse( + JenkinsPluginsDatasource.packageVersionsUrl + ); + + const versions: Record = {}; + for (const name of Object.keys(plugins ?? [])) { + versions[name] = Object.keys(plugins[name]).map((version) => ({ + version, + downloadUrl: plugins[name][version]?.url, + releaseTimestamp: plugins[name][version]?.buildDate + ? new Date(`${plugins[name][version].buildDate} UTC`) + : null, + })); + } + return versions; + } + + private async getJenkinsUpdateCenterResponse(url: string): Promise { + let response: T; + + try { + logger.debug(`jenkins-plugins: Fetching Jenkins plugins from ${url}`); + const startTime = Date.now(); + response = (await this.http.getJson(url)).body; + const durationMs = Math.round(Date.now() - startTime); + logger.debug( + { durationMs }, + `jenkins-plugins: Fetched Jenkins plugins from ${url}` + ); + } catch (err) /* istanbul ignore next */ { + this.handleGenericErrors(err); + } + + return response; + } +} diff --git a/lib/datasource/jenkins-plugins/types.ts b/lib/datasource/jenkins-plugins/types.ts index 6c67a9bca14b95..16e730a3094689 100644 --- a/lib/datasource/jenkins-plugins/types.ts +++ b/lib/datasource/jenkins-plugins/types.ts @@ -1,16 +1,3 @@ -import type { Release, ReleaseResult } from '../types'; - -export type JenkinsCacheTypes = ReleaseResult | Release[]; - -export interface JenkinsCache { - name: string; - dataUrl: string; - lastSync: Date; - cacheTimeMin: number; - cache: Record; - updatePromise?: Promise | undefined; -} - export interface JenkinsPluginInfo { name: string; scm?: string; diff --git a/lib/datasource/maven/__fixtures__/index.html b/lib/datasource/maven/__fixtures__/index.html new file mode 100644 index 00000000000000..203b42c782996a --- /dev/null +++ b/lib/datasource/maven/__fixtures__/index.html @@ -0,0 +1,40 @@ + + + + + + Central Repository: org/example/package + + + + +
+

org/example/package

+
+
+
+
+../
+0.0.1/                                                           -         -
+1.0.0/                                            2021-02-22 14:43         -
+1.0.1/                                            2021-04-12 15:51         -
+1.0.2/                                            2021-06-16 12:47         -
+2.0.0/                                            2021-06-18 16:24         -
+maven-metadata-local.xml                          2021-10-18 11:04       304
+maven-metadata-local.xml.md5                      2021-10-18 11:04        33
+maven-metadata-local.xml.sha1                     2021-10-18 11:04        41
+maven-metadata.xml                                2021-10-18 11:04      3982
+maven-metadata.xml.md5                            2021-10-18 11:04        32
+maven-metadata.xml.sha1                           2021-10-18 11:04        40
+maven-metadata.xml.sha256                         2021-10-18 11:04        64
+maven-metadata.xml.sha512                         2021-10-18 11:04       128
+		
+
+
+ + + diff --git a/lib/datasource/maven/__fixtures__/metadata.xml b/lib/datasource/maven/__fixtures__/metadata.xml index aa8588fb848e35..c10f6f2fd822d4 100644 --- a/lib/datasource/maven/__fixtures__/metadata.xml +++ b/lib/datasource/maven/__fixtures__/metadata.xml @@ -6,6 +6,7 @@ 2.0.0 2.0.0 + 0.0.1 1.0.0 1.0.1 1.0.2 diff --git a/lib/datasource/maven/__snapshots__/index.spec.ts.snap b/lib/datasource/maven/__snapshots__/index.spec.ts.snap index 799a466c033d0c..75cef939154a95 100644 --- a/lib/datasource/maven/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/maven/__snapshots__/index.spec.ts.snap @@ -18,7 +18,7 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", }, Object { "headers": Object { @@ -27,7 +27,16 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -71,8 +80,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -92,6 +101,15 @@ Array [ "method": "HEAD", "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -151,6 +169,9 @@ Object { "name": "package", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -221,7 +242,7 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", }, Object { "headers": Object { @@ -230,7 +251,16 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -274,8 +304,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -295,6 +325,15 @@ Array [ "method": "HEAD", "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -327,6 +366,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -359,6 +407,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -391,6 +448,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -423,6 +489,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/child/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/child/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -500,6 +575,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -532,6 +616,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -564,6 +657,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -605,6 +707,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -646,6 +757,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -694,7 +814,7 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", }, Object { "headers": Object { @@ -703,7 +823,16 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -747,8 +876,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -768,6 +897,15 @@ Array [ "method": "HEAD", "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -802,7 +940,7 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", }, Object { "headers": Object { @@ -811,7 +949,16 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -855,8 +1002,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -876,6 +1023,15 @@ Array [ "method": "HEAD", "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -899,6 +1055,9 @@ Array [ exports[`datasource/maven/index ignores unsupported protocols 1`] = ` Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -940,8 +1099,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "http://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "http://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -985,8 +1144,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "http://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "http://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -1006,6 +1165,15 @@ Array [ "method": "HEAD", "url": "http://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "http://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -1035,6 +1203,9 @@ Object { "name": "package", "registryUrl": "https://frontend_for_private_s3_repository/maven2", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "version": "1.0.0", }, @@ -1103,6 +1274,101 @@ Array [ ] `; +exports[`datasource/maven/index returns html-based releases 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.5-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/package-1.0.3-20200101.010003-3.pom", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.5-SNAPSHOT/package-1.0.5-SNAPSHOT.pom", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", + }, +] +`; + exports[`datasource/maven/index returns null when metadata is not found 1`] = ` Array [ Object { @@ -1114,6 +1380,15 @@ Array [ "method": "GET", "url": "https://repo.maven.apache.org/maven2/org/example/package/maven-metadata.xml", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", + }, ] `; @@ -1125,6 +1400,9 @@ Object { "name": "package", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -1159,7 +1437,7 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", }, Object { "headers": Object { @@ -1168,7 +1446,16 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -1212,8 +1499,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -1233,6 +1520,15 @@ Array [ "method": "HEAD", "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -1262,6 +1558,9 @@ Object { "name": "package", "registryUrl": "https://custom.registry.renovatebot.com", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -1307,8 +1606,8 @@ Array [ "host": "custom.registry.renovatebot.com", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "GET", - "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "method": "HEAD", + "url": "https://custom.registry.renovatebot.com/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -1357,8 +1656,8 @@ Array [ "host": "custom.registry.renovatebot.com", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -1380,6 +1679,16 @@ Array [ "method": "HEAD", "url": "https://custom.registry.renovatebot.com/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "authorization": "Bearer 123test", + "host": "custom.registry.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://custom.registry.renovatebot.com/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -1411,6 +1720,9 @@ Object { "name": "package", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -1454,7 +1766,7 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", }, Object { "headers": Object { @@ -1463,7 +1775,16 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -1507,8 +1828,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -1528,6 +1849,15 @@ Array [ "method": "HEAD", "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", @@ -1557,6 +1887,9 @@ Object { "name": "package", "registryUrl": "https://repo.maven.apache.org/maven2", "releases": Array [ + Object { + "version": "0.0.1", + }, Object { "releaseTimestamp": "2020-01-01T01:00:00.000Z", "version": "1.0.0", @@ -1600,7 +1933,7 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/index.html", }, Object { "headers": Object { @@ -1609,7 +1942,16 @@ Array [ "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.3-SNAPSHOT/maven-metadata.xml", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/0.0.1/package-0.0.1.pom", }, Object { "headers": Object { @@ -1653,8 +1995,8 @@ Array [ "host": "repo.maven.apache.org", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, - "method": "HEAD", - "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + "method": "GET", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/maven-metadata.xml", }, Object { "headers": Object { @@ -1674,6 +2016,15 @@ Array [ "method": "HEAD", "url": "https://repo.maven.apache.org/maven2/org/example/package/2.0.0/package-2.0.0.pom", }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "repo.maven.apache.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "HEAD", + "url": "https://repo.maven.apache.org/maven2/org/example/package/1.0.4-SNAPSHOT/package-1.0.4-SNAPSHOT.pom", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", diff --git a/lib/datasource/maven/index.spec.ts b/lib/datasource/maven/index.spec.ts index 5361a99e6226e4..f57fbd52a6b246 100644 --- a/lib/datasource/maven/index.spec.ts +++ b/lib/datasource/maven/index.spec.ts @@ -23,6 +23,7 @@ interface MockOpts { latest?: string; jars?: Record | null; snapshots?: SnapshotOpts[] | null; + html?: string; } function mockGenericPackage(opts: MockOpts = {}) { @@ -30,6 +31,7 @@ function mockGenericPackage(opts: MockOpts = {}) { dep = 'org.example:package', base = baseUrl, latest = '2.0.0', + html, } = opts; const meta = opts.meta === undefined ? loadFixture('metadata.xml') : opts.meta; @@ -37,6 +39,7 @@ function mockGenericPackage(opts: MockOpts = {}) { const jars = opts.jars === undefined ? { + '0.0.1': 200, '1.0.0': 200, '1.0.1': 404, '1.0.2': 500, @@ -71,6 +74,12 @@ function mockGenericPackage(opts: MockOpts = {}) { scope.get(`/${packagePath}/maven-metadata.xml`).reply(200, meta); } + if (html) { + scope.get(`/${packagePath}/index.html`).reply(200, html); + } else if (html === null) { + scope.get(`/${packagePath}/index.html`).reply(404); + } + if (pom) { scope .get(`/${packagePath}/${latest}/${artifact}-${latest}.pom`) @@ -85,9 +94,12 @@ function mockGenericPackage(opts: MockOpts = {}) { .map((x) => parseInt(x, 10)) .map((x) => (x < 10 ? `0${x}` : `${x}`)); const timestamp = `2020-01-01T${major}:${minor}:${patch}.000Z`; + const headers = version.startsWith('0.') + ? {} + : { 'Last-Modified': timestamp }; scope .head(`/${packagePath}/${version}/${artifact}-${version}.pom`) - .reply(status, '', { 'Last-Modified': timestamp }); + .reply(status, '', headers); }); } @@ -157,6 +169,8 @@ describe('datasource/maven/index', () => { it('returns null when metadata is not found', async () => { httpMock .scope(baseUrl) + .get('/org/example/package/index.html') + .reply(404) .get('/org/example/package/maven-metadata.xml') .reply(404); @@ -167,7 +181,7 @@ describe('datasource/maven/index', () => { }); it('returns releases', async () => { - mockGenericPackage(); + mockGenericPackage({ html: null }); const res = await get(); @@ -175,6 +189,36 @@ describe('datasource/maven/index', () => { expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns html-based releases', async () => { + mockGenericPackage({ + latest: '2.0.0', + jars: { '0.0.1': 200 }, // Would be the only POM we check via HEAD request + html: loadFixture('index.html'), + }); + + const res = await get(); + + expect(res).toEqual({ + display: 'org.example:package', + group: 'org.example', + homepage: 'https://package.example.org/about', + name: 'package', + registryUrl: 'https://repo.maven.apache.org/maven2', + releases: [ + { version: '0.0.1' }, + { version: '1.0.0', releaseTimestamp: '2021-02-22T14:43:00.000Z' }, + { version: '1.0.1', releaseTimestamp: '2021-04-12T15:51:00.000Z' }, + { version: '1.0.2', releaseTimestamp: '2021-06-16T12:47:00.000Z' }, + { + version: '1.0.3-SNAPSHOT', + releaseTimestamp: '2020-01-01T01:00:03.000Z', + }, + { version: '2.0.0', releaseTimestamp: '2021-06-18T16:24:00.000Z' }, + ], + }); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + it('returns releases from custom repository', async () => { mockGenericPackage({ base: baseUrlCustom }); @@ -185,7 +229,7 @@ describe('datasource/maven/index', () => { }); it('collects releases from all registry urls', async () => { - mockGenericPackage(); + mockGenericPackage({ html: null }); mockGenericPackage({ base: baseUrlCustom, meta: loadFixture('metadata-extra.xml'), @@ -201,6 +245,7 @@ describe('datasource/maven/index', () => { ); expect(releases).toMatchObject([ + { version: '0.0.1' }, { version: '1.0.0' }, { version: '1.0.3-SNAPSHOT' }, { version: '2.0.0' }, @@ -210,7 +255,7 @@ describe('datasource/maven/index', () => { }); it('falls back to next registry url', async () => { - mockGenericPackage(); + mockGenericPackage({ html: null }); httpMock .scope('https://failed_repo') .get('/org/example/package/maven-metadata.xml') @@ -268,7 +313,7 @@ describe('datasource/maven/index', () => { }); it('skips registry with invalid metadata structure', async () => { - mockGenericPackage(); + mockGenericPackage({ html: null }); httpMock .scope('https://invalid_metadata_repo') .get('/org/example/package/maven-metadata.xml') @@ -285,7 +330,7 @@ describe('datasource/maven/index', () => { }); it('skips registry with invalid XML', async () => { - mockGenericPackage(); + mockGenericPackage({ html: null }); httpMock .scope('https://invalid_metadata_repo') .get('/org/example/package/maven-metadata.xml') @@ -302,9 +347,9 @@ describe('datasource/maven/index', () => { }); it('handles optional slash at the end of registry url', async () => { - mockGenericPackage(); + mockGenericPackage({ html: null }); const resA = await get('org.example:package', baseUrl.replace(/\/+$/, '')); - mockGenericPackage(); + mockGenericPackage({ html: null }); const resB = await get('org.example:package', baseUrl.replace(/\/*$/, '/')); expect(resA).not.toBeNull(); expect(resB).not.toBeNull(); @@ -315,7 +360,7 @@ describe('datasource/maven/index', () => { it('returns null for invalid registryUrls', async () => { const res = await get( 'org.example:package', - // eslint-disable-next-line no-template-curly-in-string + '${project.baseUri}../../repository/' ); expect(res).toBeNull(); @@ -323,11 +368,11 @@ describe('datasource/maven/index', () => { it('supports scm.url values prefixed with "scm:"', async () => { const pom = loadFixture('pom.scm-prefix.xml'); - mockGenericPackage({ pom }); + mockGenericPackage({ pom, html: null }); const { sourceUrl } = await get(); - expect(sourceUrl).toEqual('https://github.com/example/test'); + expect(sourceUrl).toBe('https://github.com/example/test'); }); it('removes authentication header after redirect', async () => { @@ -390,6 +435,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); mockGenericPackage(parentPackage); @@ -409,6 +455,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); const res = await get(); @@ -450,6 +497,7 @@ describe('datasource/maven/index', () => { meta: childMeta, jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); mockGenericPackage(parentPomMock); mockGenericPackage(childPomMock); @@ -472,6 +520,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); mockGenericPackage(parentPackage); @@ -491,6 +540,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); mockGenericPackage(parentPackage); @@ -510,6 +560,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); const res = await get(); @@ -528,6 +579,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); const res = await get(); @@ -545,6 +597,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); const res = await get(); @@ -561,6 +614,7 @@ describe('datasource/maven/index', () => { latest: '2.0.0', jars: { '2.0.0': 200 }, snapshots: [], + html: null, }); const res = await get(); diff --git a/lib/datasource/maven/index.ts b/lib/datasource/maven/index.ts index 034b8d55a08186..468ee1b1574373 100644 --- a/lib/datasource/maven/index.ts +++ b/lib/datasource/maven/index.ts @@ -1,23 +1,23 @@ -import pMap from 'p-map'; +import is from '@sindresorhus/is'; +import { DateTime } from 'luxon'; +import pAll from 'p-all'; import { XmlDocument } from 'xmldoc'; import { logger } from '../../logger'; import * as packageCache from '../../util/cache/package'; +import { regEx } from '../../util/regex'; import mavenVersion from '../../versioning/maven'; import * as mavenVersioning from '../../versioning/maven'; import { compare } from '../../versioning/maven/compare'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; import { MAVEN_REPO } from './common'; -import type { - ArtifactInfoResult, - ArtifactsInfo, - MavenDependency, -} from './types'; +import type { MavenDependency, ReleaseMap } from './types'; import { + checkHttpResource, + downloadHttpProtocol, downloadMavenXml, getDependencyInfo, getDependencyParts, getMavenUrl, - isHttpResourceExists, } from './util'; export { id } from './common'; @@ -31,16 +31,17 @@ function isStableVersion(x: string): boolean { return mavenVersion.isStable(x); } -function getLatestStableVersion(releases: Release[]): string | null { - const stableVersions = releases - .map(({ version }) => version) - .filter(isStableVersion); - if (stableVersions.length) { - return stableVersions.reduce((latestVersion, version) => - compare(version, latestVersion) === 1 ? version : latestVersion - ); +function getLatestSuitableVersion(releases: Release[]): string | null { + // istanbul ignore if + if (!releases?.length) { + return null; } - return null; + const allVersions = releases.map(({ version }) => version); + const stableVersions = allVersions.filter(isStableVersion); + const versions = stableVersions.length ? stableVersions : allVersions; + return versions.reduce((latestVersion, version) => + compare(version, latestVersion) === 1 ? version : latestVersion + ); } function extractVersions(metadata: XmlDocument): string[] { @@ -52,15 +53,15 @@ function extractVersions(metadata: XmlDocument): string[] { return elements.map((el) => el.val); } -async function getVersionsFromMetadata( +async function fetchReleasesFromMetadata( dependency: MavenDependency, repoUrl: string -): Promise { +): Promise { const metadataUrl = getMavenUrl(dependency, repoUrl, 'maven-metadata.xml'); - const cacheNamespace = 'datasource-maven-metadata'; + const cacheNamespace = 'datasource-maven:metadata-xml'; const cacheKey = metadataUrl.toString(); - const cachedVersions = await packageCache.get( + const cachedVersions = await packageCache.get( cacheNamespace, cacheKey ); @@ -73,25 +74,75 @@ async function getVersionsFromMetadata( metadataUrl ); if (!mavenMetadata) { - return null; + return {}; } const versions = extractVersions(mavenMetadata); + const releaseMap = versions.reduce( + (acc, version) => ({ ...acc, [version]: null }), + {} + ); if (!authorization) { - await packageCache.set(cacheNamespace, cacheKey, versions, 30); + await packageCache.set(cacheNamespace, cacheKey, releaseMap, 30); } - return versions; + return releaseMap; } -// istanbul ignore next -function isValidArtifactsInfo( - info: ArtifactsInfo | null, - versions: string[] -): boolean { - if (!info) { - return false; +const mavenCentralHtmlVersionRegex = regEx( + '^(?:[^"]+)\\/<\\/a>\\s+(?\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d)\\s+-$', + 'i' +); + +async function addReleasesFromIndexPage( + inputReleaseMap: ReleaseMap, + dependency: MavenDependency, + repoUrl: string +): Promise { + const cacheNs = 'datasource-maven:index-html-releases'; + const cacheKey = `${repoUrl}${dependency.dependencyUrl}`; + let workingReleaseMap = await packageCache.get(cacheNs, cacheKey); + if (!workingReleaseMap) { + workingReleaseMap = {}; + let retryEarlier = false; + try { + if (repoUrl.startsWith(MAVEN_REPO)) { + const indexUrl = getMavenUrl(dependency, repoUrl, 'index.html'); + const res = await downloadHttpProtocol(indexUrl); + const { body = '' } = res; + for (const line of body.split('\n')) { + const match = line.trim().match(mavenCentralHtmlVersionRegex); + if (match) { + const { version, releaseTimestamp: timestamp } = + match?.groups || {}; + if (version && timestamp) { + const date = DateTime.fromFormat(timestamp, 'yyyy-MM-dd HH:mm', { + zone: 'UTC', + }); + if (date.isValid) { + const releaseTimestamp = date.toISO(); + workingReleaseMap[version] = { version, releaseTimestamp }; + } + } + } + } + } + } catch (err) /* istanbul ignore next */ { + retryEarlier = true; + logger.debug( + { dependency, err }, + 'Failed to get releases from index.html' + ); + } + const cacheTTL = retryEarlier ? 60 : 24 * 60; + await packageCache.set(cacheNs, cacheKey, workingReleaseMap, cacheTTL); + } + + const releaseMap = { ...inputReleaseMap }; + for (const version of Object.keys(releaseMap)) { + releaseMap[version] ||= workingReleaseMap[version] ?? null; } - return versions.every((v) => info[v] !== undefined); + + return releaseMap; } function isSnapshotVersion(version: string): boolean { @@ -169,63 +220,85 @@ async function createUrlForDependencyPom( return `${version}/${dependency.name}-${version}.pom`; } -async function filterMissingArtifacts( +async function addReleasesUsingHeadRequests( + inputReleaseMap: ReleaseMap, dependency: MavenDependency, - repoUrl: string, - versions: string[] -): Promise { - const cacheNamespace = 'datasource-maven-metadata'; + repoUrl: string +): Promise { + const releaseMap = { ...inputReleaseMap }; + + if (process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK) { + return releaseMap; + } + + const cacheNs = 'datasource-maven:head-requests'; const cacheKey = `${repoUrl}${dependency.dependencyUrl}`; - let artifactsInfo: ArtifactsInfo | null = - await packageCache.get(cacheNamespace, cacheKey); - - // If the cache contains any artifacts that we were previously unable to determine if they exist, - // retry the existence checks on them. - if (!isValidArtifactsInfo(artifactsInfo, versions)) { - // For each version, determine if there is a POM file available for it - const results: ArtifactInfoResult[] = await pMap( - versions, - async (version): Promise => { - // Create the URL that the POM file should be available at - const artifactUrl = getMavenUrl( - dependency, - repoUrl, - await createUrlForDependencyPom(version, dependency, repoUrl) - ); + let workingReleaseMap: ReleaseMap = await packageCache.get( + cacheNs, + cacheKey + ); - // Return an ArtifactInfoResult that maps the version to the result of the check if the POM file exists in the repo - return [version, await isHttpResourceExists(artifactUrl)]; - }, - { concurrency: 5 } - ); + if (!workingReleaseMap) { + workingReleaseMap = {}; - artifactsInfo = results.reduce( - (acc, [key, value]) => ({ - ...acc, - [key]: value, - }), - {} - ); + const unknownVersions = Object.entries(releaseMap) + .filter(([version, release]) => { + const isDiscoveredOutside = !!release; + const isDiscoveredInsideAndCached = !is.undefined( + workingReleaseMap[version] + ); + const isDiscovered = isDiscoveredOutside || isDiscoveredInsideAndCached; + return !isDiscovered; + }) + .map(([k]) => k); + + if (unknownVersions.length) { + let retryEarlier = false; + const queue = unknownVersions.map( + (version) => async (): Promise => { + const pomUrl = await createUrlForDependencyPom( + version, + dependency, + repoUrl + ); + const artifactUrl = getMavenUrl(dependency, repoUrl, pomUrl); + const release: Release = { version }; + + const res = await checkHttpResource(artifactUrl); + + if (res === 'error') { + retryEarlier = true; + } + + if (is.date(res)) { + release.releaseTimestamp = res.toISOString(); + } + + if (res !== 'not-found' && res !== 'error') { + workingReleaseMap[version] = release; + } + } + ); - // Retry earlier for status other than 404 - const cacheTTL = Object.values(artifactsInfo).some((x) => x === null) - ? 60 - : 24 * 60; + await pAll(queue, { concurrency: 5 }); + const cacheTTL = retryEarlier ? 60 : 24 * 60; + await packageCache.set(cacheNs, cacheKey, workingReleaseMap, cacheTTL); + } + } - await packageCache.set(cacheNamespace, cacheKey, artifactsInfo, cacheTTL); + for (const version of Object.keys(releaseMap)) { + releaseMap[version] ||= workingReleaseMap[version] ?? null; } - // Create releases for every version that exists in the repository - return versions - .filter((v) => artifactsInfo[v]) - .map((version) => { - const release: Release = { version }; - const releaseTimestamp = artifactsInfo[version]; - if (releaseTimestamp && typeof releaseTimestamp === 'string') { - release.releaseTimestamp = releaseTimestamp; - } - return release; - }); + return releaseMap; +} + +function getReleasesFromMap(releaseMap: ReleaseMap): Release[] { + const releases = Object.values(releaseMap).filter(Boolean); + if (releases.length) { + return releases; + } + return Object.keys(releaseMap).map((version) => ({ version })); } export async function getReleases({ @@ -233,48 +306,30 @@ export async function getReleases({ registryUrl, }: GetReleasesConfig): Promise { const dependency = getDependencyParts(lookupName); - let releases: Release[] = null; - const repoForVersions = {}; - const repoUrl = registryUrl.replace(/\/?$/, '/'); // TODO #12070 - logger.debug(`Looking up ${dependency.display} in repository ${repoUrl}`); - const metadataVersions = await getVersionsFromMetadata(dependency, repoUrl); - if (metadataVersions) { - if (!process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK) { - releases = await filterMissingArtifacts( - dependency, - repoUrl, - metadataVersions - ); - } + const repoUrl = registryUrl.replace(/\/?$/, '/'); // TODO #12071 - /* istanbul ignore next */ - releases = releases || metadataVersions.map((version) => ({ version })); - - const latestVersion = getLatestStableVersion(releases); - if (latestVersion) { - repoForVersions[latestVersion] = repoUrl; - } - - logger.debug(`Found ${releases.length} new releases for ${dependency.display} in repository ${repoUrl}`); // prettier-ignore - } + logger.debug(`Looking up ${dependency.display} in repository ${repoUrl}`); + let releaseMap = await fetchReleasesFromMetadata(dependency, repoUrl); + releaseMap = await addReleasesFromIndexPage(releaseMap, dependency, repoUrl); + releaseMap = await addReleasesUsingHeadRequests( + releaseMap, + dependency, + repoUrl + ); + const releases = getReleasesFromMap(releaseMap); if (!releases?.length) { return null; } - let dependencyInfo = {}; - const latestVersion = getLatestStableVersion(releases); - if (latestVersion) { - dependencyInfo = await getDependencyInfo( - dependency, - repoForVersions[latestVersion], - latestVersion - ); - } + logger.debug( + `Found ${releases.length} new releases for ${dependency.display} in repository ${repoUrl}` + ); + + const latestSuitableVersion = getLatestSuitableVersion(releases); + const dependencyInfo = + latestSuitableVersion && + (await getDependencyInfo(dependency, repoUrl, latestSuitableVersion)); - return { - ...dependency, - ...dependencyInfo, - releases, - }; + return { ...dependency, ...dependencyInfo, releases }; } diff --git a/lib/datasource/maven/types.ts b/lib/datasource/maven/types.ts index 2d314a407874b6..c66c311f1604b8 100644 --- a/lib/datasource/maven/types.ts +++ b/lib/datasource/maven/types.ts @@ -1,4 +1,5 @@ import type { XmlDocument } from 'xmldoc'; +import type { Release } from '../types'; export interface MavenDependency { display: string; @@ -12,6 +13,6 @@ export interface MavenXml { xml?: XmlDocument; } -export type ArtifactsInfo = Record; +export type ReleaseMap = Record; -export type ArtifactInfoResult = [string, boolean | string | null]; +export type HttpResourceCheckResult = 'found' | 'not-found' | 'error' | Date; diff --git a/lib/datasource/maven/util.ts b/lib/datasource/maven/util.ts index 1a637553523eb0..a0e60610143242 100644 --- a/lib/datasource/maven/util.ts +++ b/lib/datasource/maven/util.ts @@ -1,14 +1,20 @@ import url from 'url'; +import { DateTime } from 'luxon'; import { XmlDocument } from 'xmldoc'; import { HOST_DISABLED } from '../../constants/error-messages'; import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import { Http, HttpResponse } from '../../util/http'; import { regEx } from '../../util/regex'; +import { normalizeDate } from '../metadata'; import type { ReleaseResult } from '../types'; import { MAVEN_REPO, id } from './common'; -import type { MavenDependency, MavenXml } from './types'; +import type { + HttpResourceCheckResult, + MavenDependency, + MavenXml, +} from './types'; const http: Record = {}; @@ -100,18 +106,27 @@ export async function downloadHttpProtocol( } } -export async function isHttpResourceExists( +export async function checkHttpResource( pkgUrl: url.URL | string, hostType = id -): Promise { +): Promise { try { const httpClient = httpByHostType(hostType); const res = await httpClient.head(pkgUrl.toString()); const timestamp = res?.headers?.['last-modified'] as string; - return timestamp || true; + if (timestamp) { + const isoTimestamp = normalizeDate(timestamp); + if (isoTimestamp) { + const releaseDate = DateTime.fromISO(isoTimestamp, { + zone: 'UTC', + }).toJSDate(); + return releaseDate; + } + } + return 'found'; } catch (err) { if (isNotFoundError(err)) { - return false; + return 'not-found'; } const failedUrl = pkgUrl.toString(); @@ -119,7 +134,7 @@ export async function isHttpResourceExists( { failedUrl, statusCode: err.statusCode }, `Can't check HTTP resource existence` ); - return null; + return 'error'; } } diff --git a/lib/datasource/metadata.spec.ts b/lib/datasource/metadata.spec.ts index 7a05cc72f9402f..fff49ac554366d 100644 --- a/lib/datasource/metadata.spec.ts +++ b/lib/datasource/metadata.spec.ts @@ -1,5 +1,5 @@ import * as datasourceMaven from './maven'; -import { addMetaData } from './metadata'; +import { addMetaData, massageGithubUrl } from './metadata'; import * as datasourceNpm from './npm'; import { PypiDatasource } from './pypi'; import type { ReleaseResult } from './types'; @@ -76,6 +76,28 @@ describe('datasource/metadata', () => { }); }); + it('Should massage github sourceUrls', () => { + 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.dev1', + releaseTimestamp: '2017-10-24T10:09:16.000Z', + }, + { 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; + const lookupName = 'django-filter'; + + addMetaData(dep, datasource, lookupName); + expect(dep).toMatchSnapshot({ + sourceUrl: 'https://github.com/some/repo', + }); + }); + it('Should handle parsing of sourceUrls correctly for GitLab also', () => { const dep: ReleaseResult = { sourceUrl: 'https://gitlab.com/meno/dropzone/tree/master', @@ -161,7 +183,7 @@ describe('datasource/metadata', () => { const lookupName = 'io.mockk:mockk'; addMetaData(dep, datasource, lookupName); - expect(dep.sourceUrl).toEqual('https://github.com/mockk/mockk'); + expect(dep.sourceUrl).toBe('https://github.com/mockk/mockk'); }); it('Should move github homepage to sourceUrl', () => { @@ -174,7 +196,7 @@ describe('datasource/metadata', () => { const lookupName = 'io.mockk:mockk'; addMetaData(dep, datasource, lookupName); - expect(dep.sourceUrl).toEqual('https://github.com/mockk/mockk'); + expect(dep.sourceUrl).toBe('https://github.com/mockk/mockk'); expect(dep.homepage).toBeUndefined(); }); @@ -187,7 +209,7 @@ describe('datasource/metadata', () => { const lookupName = 'dropzone'; addMetaData(dep, datasource, lookupName); - expect(dep.sourceUrl).toEqual('https://gitlab.com/meno/dropzone'); + expect(dep.sourceUrl).toBe('https://gitlab.com/meno/dropzone'); }); it('Should normalize releaseTimestamp', () => { @@ -205,4 +227,30 @@ describe('datasource/metadata', () => { { releaseTimestamp: '2000-01-03T12:34:56.000Z' }, ]); }); + + it('Should massage github git@ url to valid https url', () => { + expect(massageGithubUrl('git@example.com:foo/bar')).toMatch( + 'https://example.com/foo/bar' + ); + }); + it('Should massage github http url to valid https url', () => { + expect(massageGithubUrl('http://example.com/foo/bar')).toMatch( + 'https://example.com/foo/bar' + ); + }); + it('Should massage github http and git url to valid https url', () => { + expect(massageGithubUrl('http+git://example.com/foo/bar')).toMatch( + 'https://example.com/foo/bar' + ); + }); + it('Should massage github ssh git@ url to valid https url', () => { + expect(massageGithubUrl('ssh://git@example.com/foo/bar')).toMatch( + 'https://example.com/foo/bar' + ); + }); + it('Should massage github git url to valid https url', () => { + expect(massageGithubUrl('git://example.com/foo/bar')).toMatch( + 'https://example.com/foo/bar' + ); + }); }); diff --git a/lib/datasource/metadata.ts b/lib/datasource/metadata.ts index cd92345a504a46..35cc8d50e2ff81 100644 --- a/lib/datasource/metadata.ts +++ b/lib/datasource/metadata.ts @@ -107,10 +107,23 @@ const manualSourceUrls = { }, }; -function massageGithubUrl(url: string): string { - return url +const githubPages = regEx('^https://([^.]+).github.com/([^/]+)$'); +const gitPrefix = regEx('^git:/?/?'); + +export function massageGithubUrl(url: string): string { + let massagedUrl = url; + + if (url.startsWith('git@')) { + massagedUrl = url.replace(':', '/').replace('git@', 'https://'); + } + + return massagedUrl .replace('http:', 'https:') - .replace(regEx(/^git:\/?\/?/), 'https://') + .replace('http+git:', 'https:') + .replace('https+git:', 'https:') + .replace('ssh://git@', 'https://') + .replace(gitPrefix, 'https://') + .replace(githubPages, 'https://github.com/$1/$2') .replace('www.github.com', 'github.com') .split('/') .slice(0, 5) @@ -126,7 +139,7 @@ function massageGitlabUrl(url: string): string { .replace('.git', ''); } -function normalizeDate(input: any): string | null { +export function normalizeDate(input: any): string | null { if ( typeof input === 'number' && !Number.isNaN(input) && @@ -175,7 +188,6 @@ function massageTimestamps(dep: ReleaseResult): void { } } -/* eslint-disable no-param-reassign */ export function addMetaData( dep?: ReleaseResult, datasource?: string, diff --git a/lib/datasource/npm/get.spec.ts b/lib/datasource/npm/get.spec.ts index c9e7e4baf127ab..36df1ce3bcb752 100644 --- a/lib/datasource/npm/get.spec.ts +++ b/lib/datasource/npm/get.spec.ts @@ -43,7 +43,7 @@ describe('datasource/npm/get', () => { await getDependency('@myco/test'); const trace = httpMock.getTrace(); - expect(trace[0].headers.authorization).toEqual('Bearer XXX'); + expect(trace[0].headers.authorization).toBe('Bearer XXX'); expect(trace).toMatchSnapshot(); }); }); @@ -75,7 +75,7 @@ describe('datasource/npm/get', () => { await getDependency('@myco/test'); const trace = httpMock.getTrace(); - expect(trace[0].headers.authorization).toEqual('Basic dGVzdDp0ZXN0'); + expect(trace[0].headers.authorization).toBe('Basic dGVzdDp0ZXN0'); expect(trace).toMatchSnapshot(); }); }); @@ -302,7 +302,7 @@ describe('datasource/npm/get', () => { expect(dep.sourceUrl).toBe('https://github.com/vuejs/vue.git'); expect(dep.releases[0].sourceUrl).toBeUndefined(); - expect(dep.releases[1].sourceUrl).toEqual( + expect(dep.releases[1].sourceUrl).toBe( 'https://github.com/vuejs/vue-next.git' ); }); diff --git a/lib/datasource/npm/index.spec.ts b/lib/datasource/npm/index.spec.ts index d685608c9c4695..ed891df75ad0b6 100644 --- a/lib/datasource/npm/index.spec.ts +++ b/lib/datasource/npm/index.spec.ts @@ -2,7 +2,7 @@ import mockDate from 'mockdate'; import _registryAuthToken from 'registry-auth-token'; import { getPkgReleases } from '..'; import * as httpMock from '../../../test/http-mock'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as hostRules from '../../util/host-rules'; import { id as datasource, getNpmrc, resetCache, setNpmrc } from '.'; @@ -17,7 +17,7 @@ let npmResponse: any; describe('datasource/npm/index', () => { beforeEach(() => { jest.resetAllMocks(); - setGlobalConfig(); + GlobalConfig.reset(); hostRules.clear(); resetCache(); setNpmrc(); @@ -357,8 +357,8 @@ describe('datasource/npm/index', () => { .reply(200, npmResponse); process.env.REGISTRY = 'https://registry.from-env.com'; process.env.RENOVATE_CACHE_NPM_MINUTES = '15'; - setGlobalConfig({ exposeAllEnv: true }); - // eslint-disable-next-line no-template-curly-in-string + GlobalConfig.set({ exposeAllEnv: true }); + const npmrc = 'registry=${REGISTRY}'; const res = await getPkgReleases({ datasource, depName: 'foobar', npmrc }); expect(res).toMatchSnapshot(); @@ -366,8 +366,8 @@ describe('datasource/npm/index', () => { }); it('should throw error if necessary env var is not present', () => { - setGlobalConfig({ exposeAllEnv: true }); - // eslint-disable-next-line no-template-curly-in-string + GlobalConfig.set({ exposeAllEnv: true }); + expect(() => setNpmrc('registry=${REGISTRY_MISSING}')).toThrow( Error('env-replace') ); diff --git a/lib/datasource/npm/npmrc.spec.ts b/lib/datasource/npm/npmrc.spec.ts index 6e70054cc249b9..07a6a7faa17ad1 100644 --- a/lib/datasource/npm/npmrc.spec.ts +++ b/lib/datasource/npm/npmrc.spec.ts @@ -1,5 +1,5 @@ import { mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import * as _sanitize from '../../util/sanitize'; import { getNpmrc, setNpmrc } from './npmrc'; @@ -10,7 +10,7 @@ const sanitize = mocked(_sanitize); describe('datasource/npm/npmrc', () => { beforeEach(() => { setNpmrc(''); - setGlobalConfig(); + GlobalConfig.reset(); jest.resetAllMocks(); }); @@ -21,7 +21,6 @@ describe('datasource/npm/npmrc', () => { }); it('sanitize _authtoken', () => { - // eslint-disable-next-line no-template-curly-in-string setNpmrc('//registry.test.com:_authToken=test\n_authToken=${NPM_TOKEN}'); expect(sanitize.add).toHaveBeenCalledWith('test'); expect(sanitize.add).toHaveBeenCalledTimes(1); @@ -38,10 +37,9 @@ describe('datasource/npm/npmrc', () => { }); it('sanitize _authtoken with high trust', () => { - setGlobalConfig({ exposeAllEnv: true }); + GlobalConfig.set({ exposeAllEnv: true }); process.env.TEST_TOKEN = 'test'; setNpmrc( - // eslint-disable-next-line no-template-curly-in-string '//registry.test.com:_authToken=${TEST_TOKEN}\n_authToken=\nregistry=http://localhost' ); expect(sanitize.add).toHaveBeenCalledWith('test'); diff --git a/lib/datasource/npm/npmrc.ts b/lib/datasource/npm/npmrc.ts index 7ca1d605814b5a..4c510d76259000 100644 --- a/lib/datasource/npm/npmrc.ts +++ b/lib/datasource/npm/npmrc.ts @@ -3,12 +3,13 @@ import is from '@sindresorhus/is'; import ini from 'ini'; import registryAuthToken from 'registry-auth-token'; import getRegistryUrl from 'registry-auth-token/registry-url'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import type { OutgoingHttpHeaders } from '../../util/http/types'; import { maskToken } from '../../util/mask'; import { regEx } from '../../util/regex'; import { add } from '../../util/sanitize'; +import { ensureTrailingSlash } from '../../util/url'; import type { Npmrc, PackageResolution } from './types'; let npmrc: Record = {}; @@ -61,7 +62,7 @@ export function setNpmrc(input?: string): void { npmrcRaw = input; logger.debug('Setting npmrc'); npmrc = ini.parse(input.replace(regEx(/\\n/g), '\n')); - const { exposeAllEnv } = getGlobalConfig(); + const { exposeAllEnv } = GlobalConfig.get(); for (const [key, val] of Object.entries(npmrc)) { if (!exposeAllEnv) { sanitize(key, val); @@ -112,8 +113,8 @@ export function resolvePackage(packageName: string): PackageResolution { !authInfo && npmrc && npmrc._authToken && - registryUrl.replace(regEx(/\/?$/), '/') === - npmrc.registry?.replace(/\/?$/, '/') // TODO #12070 + ensureTrailingSlash(registryUrl) === + ensureTrailingSlash(npmrc?.registry || '') ) { authInfo = { type: 'Bearer', token: npmrc._authToken }; } diff --git a/lib/datasource/nuget/index.spec.ts b/lib/datasource/nuget/index.spec.ts index d6832c70351a2a..926de848f3ed56 100644 --- a/lib/datasource/nuget/index.spec.ts +++ b/lib/datasource/nuget/index.spec.ts @@ -99,22 +99,22 @@ describe('datasource/nuget/index', () => { it('extracts feed version from registry URL hash (v3)', () => { const parsed = parseRegistryUrl('https://my-registry#protocolVersion=3'); - expect(parsed.feedUrl).toEqual('https://my-registry/'); - expect(parsed.protocolVersion).toEqual(3); + expect(parsed.feedUrl).toBe('https://my-registry/'); + expect(parsed.protocolVersion).toBe(3); }); it('extracts feed version from registry URL hash (v2)', () => { const parsed = parseRegistryUrl('https://my-registry#protocolVersion=2'); - expect(parsed.feedUrl).toEqual('https://my-registry/'); - expect(parsed.protocolVersion).toEqual(2); + expect(parsed.feedUrl).toBe('https://my-registry/'); + expect(parsed.protocolVersion).toBe(2); }); it('defaults to v2', () => { const parsed = parseRegistryUrl('https://my-registry'); - expect(parsed.feedUrl).toEqual('https://my-registry/'); - expect(parsed.protocolVersion).toEqual(2); + expect(parsed.feedUrl).toBe('https://my-registry/'); + expect(parsed.protocolVersion).toBe(2); }); it('returns null for unparseable', () => { @@ -122,9 +122,7 @@ describe('datasource/nuget/index', () => { 'https://test:malfor%5Med@test.example.com' ); - expect(parsed.feedUrl).toEqual( - 'https://test:malfor%5Med@test.example.com' - ); + expect(parsed.feedUrl).toBe('https://test:malfor%5Med@test.example.com'); expect(parsed.protocolVersion).toBeNull(); }); }); @@ -163,7 +161,7 @@ describe('datasource/nuget/index', () => { ...config, }); const trace = httpMock.getTrace(); - expect(trace[0].url).toEqual('https://my-registry/'); + expect(trace[0].url).toBe('https://my-registry/'); expect(trace).toMatchSnapshot(); }); @@ -445,7 +443,7 @@ describe('datasource/nuget/index', () => { }); expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); - expect(res.sourceUrl).not.toBeDefined(); + expect(res.sourceUrl).toBeUndefined(); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('processes real data (v3) nuspec fetch 404 error', async () => { @@ -463,7 +461,7 @@ describe('datasource/nuget/index', () => { }); expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); - expect(res.sourceUrl).not.toBeDefined(); + expect(res.sourceUrl).toBeUndefined(); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('processes real data (v2)', async () => { @@ -506,7 +504,7 @@ describe('datasource/nuget/index', () => { }); expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); - expect(res.sourceUrl).not.toBeDefined(); + expect(res.sourceUrl).toBeUndefined(); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('processes real data with no github project url (v2)', async () => { diff --git a/lib/datasource/orb/index.spec.ts b/lib/datasource/orb/index.spec.ts index 57f1aa678b9fb6..fa40d1bd2e9307 100644 --- a/lib/datasource/orb/index.spec.ts +++ b/lib/datasource/orb/index.spec.ts @@ -94,7 +94,7 @@ describe('datasource/orb/index', () => { depName: 'hyper-expanse/library-release-workflows', }); expect(res).toMatchSnapshot(); - expect(res.homepage).toEqual('https://google.com'); + expect(res.homepage).toBe('https://google.com'); expect(httpMock.getTrace()).toMatchSnapshot(); }); }); diff --git a/lib/datasource/packagist/__snapshots__/index.spec.ts.snap b/lib/datasource/packagist/__snapshots__/index.spec.ts.snap index 87a2107f708b40..c34ca81ee0da8d 100644 --- a/lib/datasource/packagist/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/packagist/__snapshots__/index.spec.ts.snap @@ -700,3 +700,33 @@ Array [ }, ] `; + +exports[`datasource/packagist/index getReleases supports providers without a hash 1`] = ` +Object { + "homepage": "https://wordpress.org/plugins/1beyt/", + "registryUrl": "https://composer.renovatebot.com", + "releases": Array [ + Object { + "gitRef": "1.0", + "version": "1.0", + }, + Object { + "gitRef": "1.1", + "version": "1.1", + }, + Object { + "gitRef": "1.4", + "version": "1.4", + }, + Object { + "gitRef": "1.5", + "version": "1.5", + }, + Object { + "gitRef": "1.5.1", + "version": "1.5.1", + }, + ], + "sourceUrl": "https://plugins.svn.wordpress.org/1beyt/", +} +`; diff --git a/lib/datasource/packagist/index.spec.ts b/lib/datasource/packagist/index.spec.ts index a4e083e2d71349..edf010c4c9f06b 100644 --- a/lib/datasource/packagist/index.spec.ts +++ b/lib/datasource/packagist/index.spec.ts @@ -305,6 +305,34 @@ describe('datasource/packagist/index', () => { expect(res).not.toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('supports providers without a hash', async () => { + const packagesJson = { + packages: [], + 'providers-url': '/p/%package%.json', + providers: { + 'wpackagist-plugin/1337-rss-feed-made-for-sharing': { + sha256: null, + }, + 'wpackagist-plugin/1beyt': { + sha256: null, + }, + }, + }; + httpMock + .scope('https://composer.renovatebot.com') + .get('/packages.json') + .reply(200, packagesJson) + .get('/p/wpackagist-plugin/1beyt.json') + .reply(200, beytJson); + const res = await getPkgReleases({ + ...config, + datasource, + versioning, + depName: 'wpackagist-plugin/1beyt', + }); + expect(res).toMatchSnapshot(); + expect(res).not.toBeNull(); + }); it('handles providers miss', async () => { const packagesJson = { packages: [], diff --git a/lib/datasource/packagist/index.ts b/lib/datasource/packagist/index.ts index 12bda735ee0e46..605662dcc3efa4 100644 --- a/lib/datasource/packagist/index.ts +++ b/lib/datasource/packagist/index.ts @@ -6,6 +6,8 @@ import * as memCache from '../../util/cache/memory'; import * as packageCache from '../../util/cache/package'; import * as hostRules from '../../util/host-rules'; import { Http, HttpOptions } from '../../util/http'; +import { regEx } from '../../util/regex'; +import { ensureTrailingSlash } from '../../util/url'; import * as composerVersioning from '../../versioning/composer'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import type { @@ -38,7 +40,7 @@ function getHostOpts(url: string): HttpOptions { } async function getRegistryMeta(regUrl: string): Promise { - const url = URL.resolve(regUrl.replace(/\/?$/, '/'), 'packages.json'); // TODO #12070 + const url = URL.resolve(ensureTrailingSlash(regUrl), 'packages.json'); const opts = getHostOpts(url); const res = (await http.getJson(url, opts)).body; const meta: RegistryMeta = { @@ -124,7 +126,7 @@ function extractDepReleases(versions: RegistryFile): ReleaseResult { dep.sourceUrl = release.source.url; } return { - version: version.replace(/^v/, ''), // TODO #12070 + version: version.replace(regEx(/^v/), ''), gitRef: version, releaseTimestamp: release.time, }; @@ -236,7 +238,7 @@ async function packageLookup( return includesPackages[name]; } let pkgUrl; - if (providerPackages?.[name]) { + if (name in providerPackages) { pkgUrl = URL.resolve( regUrl, providersUrl diff --git a/lib/datasource/pypi/__fixtures__/versions-html-hyphens.html b/lib/datasource/pypi/__fixtures__/versions-html-hyphens.html new file mode 100644 index 00000000000000..64a958fd346454 --- /dev/null +++ b/lib/datasource/pypi/__fixtures__/versions-html-hyphens.html @@ -0,0 +1,12 @@ + + + + Links for package--with-hyphens + + +

Links for package--with-hyphens

+
package--with-hyphens-2.0.0.tar.gz
+ package_with_hyphens-2.0.1-py3-none-any.whl
+ package_with_hyphens-2.0.2-py3-none-any.whl
+ package--with-hyphens-2.0.2.tar.gz
+ \ No newline at end of file diff --git a/lib/datasource/pypi/__fixtures__/versions-html-mixed-case.html b/lib/datasource/pypi/__fixtures__/versions-html-mixed-case.html new file mode 100644 index 00000000000000..a295006601c998 --- /dev/null +++ b/lib/datasource/pypi/__fixtures__/versions-html-mixed-case.html @@ -0,0 +1,12 @@ + + + + Links for PackageWithMixedCase + + +

Links for PackageWithMixedCase

+ PackageWithMixedCase-2.0.0.tar.gz
+ PackageWithMixedCase-2.0.1-py3-none-any.whl
+ PackageWithMixedCase-2.0.2-py3-none-any.whl
+ PackageWithMixedCase-2.0.2.tar.gz
+ \ No newline at end of file diff --git a/lib/datasource/pypi/__fixtures__/versions-html-with-periods.html b/lib/datasource/pypi/__fixtures__/versions-html-with-periods.html new file mode 100644 index 00000000000000..846d72d71b430b --- /dev/null +++ b/lib/datasource/pypi/__fixtures__/versions-html-with-periods.html @@ -0,0 +1,12 @@ + + + + Links for package.with.periods + + +

Links for package.with.periods

+ package.with.periods-2.0.0.tar.gz
+ package.with.periods-2.0.1-py3-none-any.whl
+ package.with.periods-2.0.2-py3-none-any.whl
+ package.with.periods-2.0.2.tar.gz
+ \ No newline at end of file diff --git a/lib/datasource/pypi/index.spec.ts b/lib/datasource/pypi/index.spec.ts index 8f919d9122aecc..1e95cb19834403 100644 --- a/lib/datasource/pypi/index.spec.ts +++ b/lib/datasource/pypi/index.spec.ts @@ -12,6 +12,9 @@ const dataRequiresPythonResponse = loadFixture( 'versions-html-data-requires-python.html' ); const mixedHyphensResponse = loadFixture('versions-html-mixed-hyphens.html'); +const mixedCaseResponse = loadFixture('versions-html-mixed-case.html'); +const withPeriodsResponse = loadFixture('versions-html-with-periods.html'); +const hyphensResponse = loadFixture('versions-html-hyphens.html'); const baseUrl = 'https://pypi.org/pypi'; const datasource = PypiDatasource.id; @@ -304,6 +307,25 @@ describe('datasource/pypi/index', () => { }); expect(res.isPrivate).toBeTrue(); }); + it('process data from simple endpoint with hyphens', async () => { + httpMock + .scope('https://pypi.org/simple/') + .get('/package-with-hyphens/') + .reply(200, hyphensResponse); + const config = { + registryUrls: ['https://pypi.org/simple/'], + }; + const res = await getPkgReleases({ + datasource, + ...config, + depName: 'package--with-hyphens', + }); + expect(res.releases).toMatchObject([ + { version: '2.0.0' }, + { version: '2.0.1' }, + { version: '2.0.2' }, + ]); + }); it('process data from simple endpoint with hyphens replaced with underscores', async () => { httpMock .scope('https://pypi.org/simple/') @@ -322,6 +344,63 @@ describe('datasource/pypi/index', () => { ).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('process data from simple endpoint with mixed-case characters', async () => { + httpMock + .scope('https://pypi.org/simple/') + .get('/packagewithmixedcase/') + .reply(200, mixedCaseResponse); + const config = { + registryUrls: ['https://pypi.org/simple/'], + }; + const res = await getPkgReleases({ + datasource, + ...config, + depName: 'PackageWithMixedCase', + }); + expect(res.releases).toMatchObject([ + { version: '2.0.0' }, + { version: '2.0.1' }, + { version: '2.0.2' }, + ]); + }); + it('process data from simple endpoint with mixed-case characters when using lower case dependency name', async () => { + httpMock + .scope('https://pypi.org/simple/') + .get('/packagewithmixedcase/') + .reply(200, mixedCaseResponse); + const config = { + registryUrls: ['https://pypi.org/simple/'], + }; + const res = await getPkgReleases({ + datasource, + ...config, + depName: 'packagewithmixedcase', + }); + expect(res.releases).toMatchObject([ + { version: '2.0.0' }, + { version: '2.0.1' }, + { version: '2.0.2' }, + ]); + }); + it('process data from simple endpoint with periods', async () => { + httpMock + .scope('https://pypi.org/simple/') + .get('/package-with-periods/') + .reply(200, withPeriodsResponse); + const config = { + registryUrls: ['https://pypi.org/simple/'], + }; + const res = await getPkgReleases({ + datasource, + ...config, + depName: 'package.with.periods', + }); + expect(res.releases).toMatchObject([ + { version: '2.0.0' }, + { version: '2.0.1' }, + { version: '2.0.2' }, + ]); + }); it('returns null for empty response', async () => { httpMock .scope('https://pypi.org/simple/') diff --git a/lib/datasource/pypi/index.ts b/lib/datasource/pypi/index.ts index bd143aec398289..e2705686601c94 100644 --- a/lib/datasource/pypi/index.ts +++ b/lib/datasource/pypi/index.ts @@ -73,6 +73,10 @@ export class PypiDatasource extends Datasource { } private static normalizeName(input: string): string { + return input.toLowerCase().replace(regEx(/_/g), '-'); + } + + private static normalizeNameForUrlLookup(input: string): string { return input.toLowerCase().replace(regEx(/(_|\.|-)+/g), '-'); } @@ -80,7 +84,10 @@ export class PypiDatasource extends Datasource { packageName: string, hostUrl: string ): Promise { - const lookupUrl = url.resolve(hostUrl, `${packageName}/json`); + const lookupUrl = url.resolve( + hostUrl, + `${PypiDatasource.normalizeNameForUrlLookup(packageName)}/json` + ); const dependency: ReleaseResult = { releases: null }; logger.trace({ lookupUrl }, 'Pypi api got lookup'); const rep = await this.http.getJson(lookupUrl); @@ -164,27 +171,25 @@ export class PypiDatasource extends Datasource { text: string, packageName: string ): string | null { - const srcPrefixes = [ - `${packageName}-`, - `${packageName.replace(regEx(/-/g), '_')}-`, - ]; - for (const prefix of srcPrefixes) { - const suffix = '.tar.gz'; - if (text.startsWith(prefix) && text.endsWith(suffix)) { - return text.replace(prefix, '').replace(regEx(/\.tar\.gz$/), ''); // TODO #12071 - } + // source packages + const srcText = PypiDatasource.normalizeName(text); + const srcPrefix = `${packageName}-`; + const srcSuffix = '.tar.gz'; + if (srcText.startsWith(srcPrefix) && srcText.endsWith(srcSuffix)) { + return srcText.replace(srcPrefix, '').replace(regEx(/\.tar\.gz$/), ''); // TODO #12071 } // pep-0427 wheel packages // {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl. + const wheelText = text.toLowerCase(); const wheelPrefix = packageName.replace(regEx(/[^\w\d.]+/g), '_') + '-'; const wheelSuffix = '.whl'; if ( - text.startsWith(wheelPrefix) && - text.endsWith(wheelSuffix) && - text.split('-').length > 2 + wheelText.startsWith(wheelPrefix) && + wheelText.endsWith(wheelSuffix) && + wheelText.split('-').length > 2 ) { - return text.split('-')[1]; + return wheelText.split('-')[1]; } return null; @@ -210,7 +215,10 @@ export class PypiDatasource extends Datasource { packageName: string, hostUrl: string ): Promise { - const lookupUrl = url.resolve(hostUrl, ensureTrailingSlash(packageName)); + const lookupUrl = url.resolve( + hostUrl, + ensureTrailingSlash(PypiDatasource.normalizeNameForUrlLookup(packageName)) + ); const dependency: ReleaseResult = { releases: null }; const response = await this.http.get(lookupUrl); const dep = response?.body; diff --git a/lib/datasource/repology/index.spec.ts b/lib/datasource/repology/index.spec.ts index c7b7e1378320b6..7f9a67c136f32a 100644 --- a/lib/datasource/repology/index.spec.ts +++ b/lib/datasource/repology/index.spec.ts @@ -221,7 +221,7 @@ describe('datasource/repology/index', () => { }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(1); - expect(res.releases[0].version).toEqual('1.14.2-2+deb10u1'); + expect(res.releases[0].version).toBe('1.14.2-2+deb10u1'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -241,7 +241,7 @@ describe('datasource/repology/index', () => { }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(1); - expect(res.releases[0].version).toEqual('1.181'); + expect(res.releases[0].version).toBe('1.181'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -258,7 +258,7 @@ describe('datasource/repology/index', () => { }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(1); - expect(res.releases[0].version).toEqual('1.181'); + expect(res.releases[0].version).toBe('1.181'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -275,7 +275,7 @@ describe('datasource/repology/index', () => { }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(1); - expect(res.releases[0].version).toEqual('9.3.0-r2'); + expect(res.releases[0].version).toBe('9.3.0-r2'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -292,7 +292,7 @@ describe('datasource/repology/index', () => { }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(1); - expect(res.releases[0].version).toEqual('12.2-4+deb10u1'); + expect(res.releases[0].version).toBe('12.2-4+deb10u1'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -312,8 +312,8 @@ describe('datasource/repology/index', () => { }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(6); - expect(res.releases[0].version).toEqual('1:11.0.7.10-1.el8_1'); - expect(res.releases[5].version).toEqual('1:11.0.9.11-3.el8_3'); + expect(res.releases[0].version).toBe('1:11.0.7.10-1.el8_1'); + expect(res.releases[5].version).toBe('1:11.0.9.11-3.el8_3'); expect(httpMock.getTrace()).toMatchSnapshot(); }); diff --git a/lib/datasource/ruby-version/index.ts b/lib/datasource/ruby-version/index.ts index c5c845b7b70317..ca7646277cdcb9 100644 --- a/lib/datasource/ruby-version/index.ts +++ b/lib/datasource/ruby-version/index.ts @@ -56,7 +56,6 @@ export class RubyVersionDatasource extends Datasource { return res; } - // eslint-disable-next-line class-methods-use-this override handleSpecificErrors(err: HttpError): never | void { throw new ExternalHostError(err); } diff --git a/lib/datasource/rubygems/common.ts b/lib/datasource/rubygems/common.ts deleted file mode 100644 index ce52a088a1e9c6..00000000000000 --- a/lib/datasource/rubygems/common.ts +++ /dev/null @@ -1,40 +0,0 @@ -import Marshal from 'marshal'; -import urlJoin from 'url-join'; -import { logger } from '../../logger'; -import { Http } from '../../util/http'; -import { getQueryString } from '../../util/url'; - -export const id = 'rubygems'; -export const http = new Http(id); - -export const knownFallbackHosts = ['rubygems.pkg.github.com', 'gitlab.com']; - -export async function fetchJson( - dependency: string, - registry: string, - path: string -): Promise { - const url = urlJoin(registry, path, `${dependency}.json`); - - logger.trace({ registry, dependency, url }, `RubyGems lookup request`); - const response = (await http.getJson(url)) || { - body: undefined, - }; - - return response.body; -} - -export async function fetchBuffer( - dependency: string, - registry: string, - path: string -): Promise { - const url = `${urlJoin(registry, path)}?${getQueryString({ - gems: dependency, - })}`; - - logger.trace({ registry, dependency, url }, `RubyGems lookup request`); - const response = await http.getBuffer(url); - - return new Marshal(response.body).parsed as T; -} diff --git a/lib/datasource/rubygems/get-rubygems-org.ts b/lib/datasource/rubygems/get-rubygems-org.ts index 7ca2bdcda8daf3..3be067768bda5c 100644 --- a/lib/datasource/rubygems/get-rubygems-org.ts +++ b/lib/datasource/rubygems/get-rubygems-org.ts @@ -1,8 +1,8 @@ import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import { getElapsedMinutes } from '../../util/date'; -import type { ReleaseResult } from '../types'; -import { http } from './common'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, ReleaseResult } from '../types'; let lastSync = new Date('2000-01-01'); let packageReleases: Record = Object.create(null); // Because we might need a "constructor" key @@ -15,38 +15,69 @@ export function resetCache(): void { contentLength = 0; } -/* https://bugs.chromium.org/p/v8/issues/detail?id=2869 */ -const copystr = (x: string): string => (' ' + x).slice(1); +export class RubyGemsOrgDatasource extends Datasource { + constructor(override readonly id: string) { + super(id); + } -async function updateRubyGemsVersions(): Promise { - const url = 'https://rubygems.org/versions'; - const options = { - headers: { - 'accept-encoding': 'identity', - range: `bytes=${contentLength}-`, - }, - }; - let newLines: string; - try { - logger.debug('Rubygems: Fetching rubygems.org versions'); - const startTime = Date.now(); - newLines = (await http.get(url, options)).body; - const durationMs = Math.round(Date.now() - startTime); - logger.debug({ durationMs }, 'Rubygems: Fetched rubygems.org versions'); - } catch (err) /* istanbul ignore next */ { - if (err.statusCode !== 416) { - contentLength = 0; - packageReleases = Object.create(null); // Because we might need a "constructor" key - throw new ExternalHostError( - new Error('Rubygems fetch error - need to reset cache') - ); + async getReleases({ + lookupName, + }: GetReleasesConfig): Promise { + logger.debug(`getRubygemsOrgDependency(${lookupName})`); + await this.syncVersions(); + if (!packageReleases[lookupName]) { + return null; + } + const dep: ReleaseResult = { + releases: packageReleases[lookupName].map((version) => ({ + version, + })), + }; + return dep; + } + + /** + * https://bugs.chromium.org/p/v8/issues/detail?id=2869 + */ + private static copystr(x: string): string { + return (' ' + x).slice(1); + } + + async updateRubyGemsVersions(): Promise { + const url = 'https://rubygems.org/versions'; + const options = { + headers: { + 'accept-encoding': 'identity', + range: `bytes=${contentLength}-`, + }, + }; + let newLines: string; + try { + logger.debug('Rubygems: Fetching rubygems.org versions'); + const startTime = Date.now(); + newLines = (await this.http.get(url, options)).body; + const durationMs = Math.round(Date.now() - startTime); + logger.debug({ durationMs }, 'Rubygems: Fetched rubygems.org versions'); + } catch (err) /* istanbul ignore next */ { + if (err.statusCode !== 416) { + contentLength = 0; + packageReleases = Object.create(null); // Because we might need a "constructor" key + throw new ExternalHostError( + new Error('Rubygems fetch error - need to reset cache') + ); + } + logger.debug('Rubygems: No update'); + lastSync = new Date(); + return; + } + + for (const line of newLines.split('\n')) { + RubyGemsOrgDatasource.processLine(line); } - logger.debug('Rubygems: No update'); lastSync = new Date(); - return; } - function processLine(line: string): void { + private static processLine(line: string): void { let split: string[]; let pkg: string; let versions: string; @@ -57,7 +88,7 @@ async function updateRubyGemsVersions(): Promise { } split = l.split(' '); [pkg, versions] = split; - pkg = copystr(pkg); + pkg = RubyGemsOrgDatasource.copystr(pkg); packageReleases[pkg] = packageReleases[pkg] || []; const lineVersions = versions.split(',').map((version) => version.trim()); for (const lineVersion of lineVersions) { @@ -68,7 +99,7 @@ async function updateRubyGemsVersions(): Promise { (version) => version !== deletedVersion ); } else { - packageReleases[pkg].push(copystr(lineVersion)); + packageReleases[pkg].push(RubyGemsOrgDatasource.copystr(lineVersion)); } } } catch (err) /* istanbul ignore next */ { @@ -79,38 +110,19 @@ async function updateRubyGemsVersions(): Promise { } } - for (const line of newLines.split('\n')) { - processLine(line); + private static isDataStale(): boolean { + return getElapsedMinutes(lastSync) >= 5; } - lastSync = new Date(); -} - -function isDataStale(): boolean { - return getElapsedMinutes(lastSync) >= 5; -} -let updateRubyGemsVersionsPromise: Promise | undefined; + updateRubyGemsVersionsPromise: Promise | undefined; -async function syncVersions(): Promise { - if (isDataStale()) { - updateRubyGemsVersionsPromise = - // eslint-disable-next-line @typescript-eslint/no-misused-promises - updateRubyGemsVersionsPromise || updateRubyGemsVersions(); - await updateRubyGemsVersionsPromise; - updateRubyGemsVersionsPromise = null; - } -} - -export async function getRubygemsOrgDependency( - lookupName: string -): Promise { - logger.debug(`getRubygemsOrgDependency(${lookupName})`); - await syncVersions(); - if (!packageReleases[lookupName]) { - return null; + async syncVersions(): Promise { + if (RubyGemsOrgDatasource.isDataStale()) { + this.updateRubyGemsVersionsPromise = + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.updateRubyGemsVersionsPromise || this.updateRubyGemsVersions(); + await this.updateRubyGemsVersionsPromise; + this.updateRubyGemsVersionsPromise = null; + } } - const dep: ReleaseResult = { - releases: packageReleases[lookupName].map((version) => ({ version })), - }; - return dep; } diff --git a/lib/datasource/rubygems/get.ts b/lib/datasource/rubygems/get.ts index 6d1d491b198c2a..a6b45f197ef8a9 100644 --- a/lib/datasource/rubygems/get.ts +++ b/lib/datasource/rubygems/get.ts @@ -1,7 +1,9 @@ +import Marshal from 'marshal'; import { logger } from '../../logger'; import { HttpError } from '../../util/http/types'; -import type { Release, ReleaseResult } from '../types'; -import { fetchBuffer, fetchJson } from './common'; +import { getQueryString, joinUrlParts, parseUrl } from '../../util/url'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; import type { JsonGemVersions, JsonGemsInfo, @@ -12,114 +14,164 @@ const INFO_PATH = '/api/v1/gems'; const VERSIONS_PATH = '/api/v1/versions'; const DEPENDENCIES_PATH = '/api/v1/dependencies'; -export async function getDependencyFallback( - dependency: string, - registry: string -): Promise { - logger.debug( - { dependency, api: DEPENDENCIES_PATH }, - 'RubyGems lookup for dependency' - ); - const info = await fetchBuffer( - dependency, - registry, - DEPENDENCIES_PATH - ); - if (!info || info.length === 0) { - return null; +export class InternalRubyGemsDatasource extends Datasource { + constructor(override readonly id: string) { + super(id); } - const releases = info.map(({ number: version, platform: rubyPlatform }) => ({ - version, - rubyPlatform, - })); - return { - releases, - homepage: null, - sourceUrl: null, - changelogUrl: null, - }; -} -export async function getDependency( - dependency: string, - registry: string -): Promise { - logger.debug( - { dependency, api: INFO_PATH }, - 'RubyGems lookup for dependency' - ); - let info: JsonGemsInfo; - - try { - info = await fetchJson(dependency, registry, INFO_PATH); - } catch (error) { - // fallback to deps api on 404 - if (error instanceof HttpError && error.response?.statusCode === 404) { - return await getDependencyFallback(dependency, registry); + private knownFallbackHosts = ['rubygems.pkg.github.com', 'gitlab.com']; + + override getReleases({ + lookupName, + registryUrl, + }: GetReleasesConfig): Promise { + if (this.knownFallbackHosts.includes(parseUrl(registryUrl)?.hostname)) { + return this.getDependencyFallback(lookupName, registryUrl); } - throw error; + return this.getDependency(lookupName, registryUrl); } - if (!info) { - logger.debug({ dependency }, 'RubyGems package not found.'); - return null; + async getDependencyFallback( + dependency: string, + registry: string + ): Promise { + logger.debug( + { dependency, api: DEPENDENCIES_PATH }, + 'RubyGems lookup for dependency' + ); + const info = await this.fetchBuffer( + dependency, + registry, + DEPENDENCIES_PATH + ); + if (!info || info.length === 0) { + return null; + } + const releases = info.map( + ({ number: version, platform: rubyPlatform }) => ({ + version, + rubyPlatform, + }) + ); + return { + releases, + homepage: null, + sourceUrl: null, + changelogUrl: null, + }; } - if (dependency.toLowerCase() !== info.name.toLowerCase()) { - logger.warn( - { lookup: dependency, returned: info.name }, - 'Lookup name does not match with returned.' + async getDependency( + dependency: string, + registry: string + ): Promise { + logger.debug( + { dependency, api: INFO_PATH }, + 'RubyGems lookup for dependency' ); - return null; - } + let info: JsonGemsInfo; + + try { + info = await this.fetchJson(dependency, registry, INFO_PATH); + } catch (error) { + // fallback to deps api on 404 + if (error instanceof HttpError && error.response?.statusCode === 404) { + return await this.getDependencyFallback(dependency, registry); + } + throw error; + } - let versions: JsonGemVersions[] = []; - let releases: Release[] = []; - try { - versions = await fetchJson(dependency, registry, VERSIONS_PATH); - } catch (err) { - if (err.statusCode === 400 || err.statusCode === 404) { - logger.debug( - { registry }, - 'versions endpoint returns error - falling back to info endpoint' + if (!info) { + logger.debug({ dependency }, 'RubyGems package not found.'); + return null; + } + + if (dependency.toLowerCase() !== info.name.toLowerCase()) { + logger.warn( + { lookup: dependency, returned: info.name }, + 'Lookup name does not match with returned.' ); + return null; + } + + let versions: JsonGemVersions[] = []; + let releases: Release[] = []; + try { + versions = await this.fetchJson(dependency, registry, VERSIONS_PATH); + } catch (err) { + if (err.statusCode === 400 || err.statusCode === 404) { + logger.debug( + { registry }, + 'versions endpoint returns error - falling back to info endpoint' + ); + } else { + throw err; + } + } + + // TODO: invalid properties for `Release` see #11312 + + if (versions.length === 0 && info.version) { + logger.warn('falling back to the version from the info endpoint'); + releases = [ + { + version: info.version, + rubyPlatform: info.platform, + } as Release, + ]; } else { - throw err; + releases = versions.map( + ({ + number: version, + platform: rubyPlatform, + created_at: releaseTimestamp, + rubygems_version: rubygemsVersion, + ruby_version: rubyVersion, + }) => ({ + version, + rubyPlatform, + releaseTimestamp, + rubygemsVersion, + rubyVersion, + }) + ); } + + return { + releases, + homepage: info.homepage_uri, + sourceUrl: info.source_code_uri, + changelogUrl: info.changelog_uri, + }; } - // TODO: invalid properties for `Release` see #11312 - - if (versions.length === 0 && info.version) { - logger.warn('falling back to the version from the info endpoint'); - releases = [ - { - version: info.version, - rubyPlatform: info.platform, - } as Release, - ]; - } else { - releases = versions.map( - ({ - number: version, - platform: rubyPlatform, - created_at: releaseTimestamp, - rubygems_version: rubygemsVersion, - ruby_version: rubyVersion, - }) => ({ - version, - rubyPlatform, - releaseTimestamp, - rubygemsVersion, - rubyVersion, - }) - ); + private async fetchJson( + dependency: string, + registry: string, + path: string + ): Promise { + const url = joinUrlParts(registry, path, `${dependency}.json`); + + logger.trace({ registry, dependency, url }, `RubyGems lookup request`); + const response = (await this.http.getJson(url)) || { + body: undefined, + }; + + return response.body; } - return { - releases, - homepage: info.homepage_uri, - sourceUrl: info.source_code_uri, - changelogUrl: info.changelog_uri, - }; + private async fetchBuffer( + dependency: string, + registry: string, + path: string + ): Promise { + const url = `${joinUrlParts(registry, path)}?${getQueryString({ + gems: dependency, + })}`; + + logger.trace({ registry, dependency, url }, `RubyGems lookup request`); + const response = await this.http.getBuffer(url); + + return new Marshal(response.body).parsed as T; + } } diff --git a/lib/datasource/rubygems/index.spec.ts b/lib/datasource/rubygems/index.spec.ts index 913204eafb2b5b..71451f040f6feb 100644 --- a/lib/datasource/rubygems/index.spec.ts +++ b/lib/datasource/rubygems/index.spec.ts @@ -7,7 +7,7 @@ import { } from '../../../test/util'; import * as rubyVersioning from '../../versioning/ruby'; import { resetCache } from './get-rubygems-org'; -import * as rubygems from '.'; +import { RubyGemsDatasource } from '.'; const rubygemsOrgVersions = loadFixture('rubygems-org.txt'); const railsInfo = loadJsonFixture('rails/info.json'); @@ -21,7 +21,7 @@ describe('datasource/rubygems/index', () => { const params = { versioning: rubyVersioning.id, - datasource: rubygems.id, + datasource: RubyGemsDatasource.id, depName: 'rails', registryUrls: [ 'https://thirdparty.com', diff --git a/lib/datasource/rubygems/index.ts b/lib/datasource/rubygems/index.ts index 83ca50a1de8f63..74da9e61e9a889 100644 --- a/lib/datasource/rubygems/index.ts +++ b/lib/datasource/rubygems/index.ts @@ -1,8 +1,49 @@ +import { cache } from '../../util/cache/package/decorator'; +import { parseUrl } from '../../util/url'; import * as rubyVersioning from '../../versioning/ruby'; +import { Datasource } from '../datasource'; +import { GetReleasesConfig, ReleaseResult } from '../types'; +import { InternalRubyGemsDatasource } from './get'; +import { RubyGemsOrgDatasource } from './get-rubygems-org'; -export { getReleases } from './releases'; -export { id } from './common'; -export const customRegistrySupport = true; -export const defaultRegistryUrls = ['https://rubygems.org']; -export const defaultVersioning = rubyVersioning.id; -export const registryStrategy = 'hunt'; +export class RubyGemsDatasource extends Datasource { + static readonly id = 'rubygems'; + + constructor() { + super(RubyGemsDatasource.id); + this.rubyGemsOrgDatasource = new RubyGemsOrgDatasource( + RubyGemsDatasource.id + ); + this.internalRubyGemsDatasource = new InternalRubyGemsDatasource( + RubyGemsDatasource.id + ); + } + + override readonly defaultRegistryUrls = ['https://rubygems.org']; + + override readonly defaultVersioning = rubyVersioning.id; + + override readonly registryStrategy = 'hunt'; + + private readonly rubyGemsOrgDatasource: RubyGemsOrgDatasource; + + private readonly internalRubyGemsDatasource: InternalRubyGemsDatasource; + + @cache({ + namespace: `datasource-${RubyGemsDatasource.id}`, + key: ({ registryUrl, lookupName }: GetReleasesConfig) => + `${registryUrl}/${lookupName}`, + }) + getReleases({ + lookupName, + registryUrl, + }: GetReleasesConfig): Promise { + if (parseUrl(registryUrl)?.hostname === 'rubygems.org') { + return this.rubyGemsOrgDatasource.getReleases({ lookupName }); + } + return this.internalRubyGemsDatasource.getReleases({ + lookupName, + registryUrl, + }); + } +} diff --git a/lib/datasource/rubygems/releases.ts b/lib/datasource/rubygems/releases.ts deleted file mode 100644 index 5a4a2c9e9115bc..00000000000000 --- a/lib/datasource/rubygems/releases.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { parseUrl } from '../../util/url'; -import type { GetReleasesConfig, ReleaseResult } from '../types'; -import { knownFallbackHosts } from './common'; -import { getDependency, getDependencyFallback } from './get'; -import { getRubygemsOrgDependency } from './get-rubygems-org'; - -export function getReleases({ - lookupName, - registryUrl, -}: GetReleasesConfig): Promise { - if (parseUrl(registryUrl)?.hostname === 'rubygems.org') { - return getRubygemsOrgDependency(lookupName); - } - if (knownFallbackHosts.includes(parseUrl(registryUrl)?.hostname)) { - return getDependencyFallback(lookupName, registryUrl); - } - return getDependency(lookupName, registryUrl); -} diff --git a/lib/datasource/sbt-package/index.ts b/lib/datasource/sbt-package/index.ts index dc92514efc43b0..bd34c2d1d94cc7 100644 --- a/lib/datasource/sbt-package/index.ts +++ b/lib/datasource/sbt-package/index.ts @@ -1,6 +1,7 @@ import { XmlDocument } from 'xmldoc'; import { logger } from '../../logger'; import { regEx } from '../../util/regex'; +import { ensureTrailingSlash } from '../../util/url'; import * as ivyVersioning from '../../versioning/ivy'; import { compare } from '../../versioning/maven/compare'; import { MAVEN_REPO } from '../maven/common'; @@ -14,8 +15,6 @@ export const defaultRegistryUrls = [MAVEN_REPO]; export const defaultVersioning = ivyVersioning.id; export const registryStrategy = 'hunt'; -const ensureTrailingSlash = (str: string): string => str.replace(/\/?$/, '/'); // TODO #12070 - export async function getArtifactSubdirs( searchRoot: string, artifact: string, diff --git a/lib/datasource/terraform-module/index.ts b/lib/datasource/terraform-module/index.ts index 84f287d57b4b80..0c6d4a44da6496 100644 --- a/lib/datasource/terraform-module/index.ts +++ b/lib/datasource/terraform-module/index.ts @@ -83,7 +83,6 @@ export class TerraformModuleDatasource extends TerraformDatasource { return dep; } - // eslint-disable-next-line class-methods-use-this override handleSpecificErrors(err: HttpError): void { const failureCodes = ['EAI_AGAIN']; // istanbul ignore if diff --git a/lib/datasource/types.ts b/lib/datasource/types.ts index 1d271a8a9ad17d..c03502dde6a2c5 100644 --- a/lib/datasource/types.ts +++ b/lib/datasource/types.ts @@ -28,6 +28,8 @@ export interface GetPkgReleasesConfig extends ReleasesConfigBase { versioning?: string; extractVersion?: string; constraints?: Record; + replacementName?: string; + replacementVersion?: string; } export interface Release { @@ -60,6 +62,8 @@ export interface ReleaseResult { sourceUrl?: string; sourceDirectory?: string; registryUrl?: string; + replacementName?: string; + replacementVersion?: string; } export interface DatasourceApi { diff --git a/lib/logger/__snapshots__/err-serializer.spec.ts.snap b/lib/logger/__snapshots__/err-serializer.spec.ts.snap index 28a773719a6d8d..ce8133fcf296e8 100644 --- a/lib/logger/__snapshots__/err-serializer.spec.ts.snap +++ b/lib/logger/__snapshots__/err-serializer.spec.ts.snap @@ -51,7 +51,7 @@ Array [ exports[`logger/err-serializer got sanitize http error 2`] = ` Object { - "code": undefined, + "code": "ERR_NON_2XX_3XX_RESPONSE", "message": "Response code 412 (Precondition Failed)", "name": "HTTPError", "options": Object { diff --git a/lib/logger/cmd-serializer.ts b/lib/logger/cmd-serializer.ts index ea7741c52bb575..5142e5c6ca8c69 100644 --- a/lib/logger/cmd-serializer.ts +++ b/lib/logger/cmd-serializer.ts @@ -3,7 +3,7 @@ export default function cmdSerializer( cmd: string | string[] ): string | string[] { if (typeof cmd === 'string') { - return cmd.replace(/https:\/\/[^@]*@/g, 'https://**redacted**@'); // TODO #12070 + return cmd.replace(/https:\/\/[^@]*@/g, 'https://**redacted**@'); // TODO #12874 } return cmd; } diff --git a/lib/logger/config-serializer.ts b/lib/logger/config-serializer.ts index 9f6a33dd180669..cc54936e583c40 100644 --- a/lib/logger/config-serializer.ts +++ b/lib/logger/config-serializer.ts @@ -13,19 +13,18 @@ export default function configSerializer( ]; const arrayFields = ['packageFiles', 'upgrades']; - return traverse(config).map( - // eslint-disable-next-line array-callback-return - function scrub(val: string) { - if (val && templateFields.includes(this.key)) { + return traverse(config).map(function scrub(val: string) { + if (this.key && val) { + if (templateFields.includes(this.key)) { this.update('[Template]'); } - if (val && contentFields.includes(this.key)) { + if (contentFields.includes(this.key)) { this.update('[content]'); } // istanbul ignore if - if (val && arrayFields.includes(this.key)) { + if (arrayFields.includes(this.key)) { this.update('[Array]'); } } - ); + }); } diff --git a/lib/logger/err-serializer.ts b/lib/logger/err-serializer.ts index 23d81cc111af80..d33cc614606e3a 100644 --- a/lib/logger/err-serializer.ts +++ b/lib/logger/err-serializer.ts @@ -12,7 +12,7 @@ export default function errSerializer(err: Error): any { const val = response[field]; if (is.string(val)) { response[field] = val.replace( - /https:\/\/[^@]*?@/g, // TODO #12070 #12071 + /https:\/\/[^@]*?@/g, // TODO #12874 'https://**redacted**@' ); } diff --git a/lib/logger/index.spec.ts b/lib/logger/index.spec.ts index 29d7b931adf803..3a3e10966360e1 100644 --- a/lib/logger/index.spec.ts +++ b/lib/logger/index.spec.ts @@ -25,7 +25,7 @@ describe('logger/index', () => { }); it('sets and gets context', () => { setContext('123test'); - expect(getContext()).toEqual('123test'); + expect(getContext()).toBe('123test'); }); it('supports logging with metadata', () => { expect(() => logger.debug({ some: 'meta' }, 'some meta')).not.toThrow(); @@ -92,10 +92,10 @@ describe('logger/index', () => { }); it('supports file-based logging', () => { - let chunk = null; + let chunk = ''; fs.createWriteStream.mockReturnValueOnce({ writable: true, - write(x) { + write(x: string) { chunk = x; }, }); @@ -108,14 +108,14 @@ describe('logger/index', () => { logger.error('foo'); - expect(JSON.parse(chunk).msg).toEqual('foo'); + expect(JSON.parse(chunk).msg).toBe('foo'); }); it('handles cycles', () => { - let logged = null; + let logged: Record = {}; fs.createWriteStream.mockReturnValueOnce({ writable: true, - write(x) { + write(x: string) { logged = JSON.parse(x); }, }); @@ -126,21 +126,21 @@ describe('logger/index', () => { level: 'error', }); - const meta = { foo: null, bar: [] }; + const meta: Record = { foo: null, bar: [] }; meta.foo = meta; meta.bar.push(meta); logger.error(meta, 'foo'); - expect(logged.msg).toEqual('foo'); - expect(logged.foo.foo).toEqual('[Circular]'); + expect(logged.msg).toBe('foo'); + expect(logged.foo.foo).toBe('[Circular]'); expect(logged.foo.bar).toEqual(['[Circular]']); - expect(logged.bar).toEqual('[Circular]'); + expect(logged.bar).toBe('[Circular]'); }); it('sanitizes secrets', () => { - let logged = null; + let logged: Record = {}; fs.createWriteStream.mockReturnValueOnce({ writable: true, - write(x) { + write(x: string) { logged = JSON.parse(x); }, }); @@ -152,6 +152,10 @@ describe('logger/index', () => { }); add({ password: 'secret"password' }); + class SomeClass { + constructor(public field: string) {} + } + logger.error({ foo: 'secret"password', bar: ['somethingelse', 'secret"password'], @@ -162,16 +166,20 @@ describe('logger/index', () => { secrets: { foo: 'barsecret', }, + someFn: () => 'secret"password', + someObject: new SomeClass('secret"password'), }); - expect(logged.foo).not.toEqual('secret"password'); - expect(logged.bar[0]).toEqual('somethingelse'); + expect(logged.foo).not.toBe('secret"password'); + expect(logged.bar[0]).toBe('somethingelse'); expect(logged.foo).toContain('redacted'); expect(logged.bar[1]).toContain('redacted'); - expect(logged.npmToken).not.toEqual('token'); - expect(logged.buffer).toEqual('[content]'); - expect(logged.content).toEqual('[content]'); - expect(logged.prBody).toEqual('[Template]'); - expect(logged.secrets.foo).toEqual('***********'); + expect(logged.npmToken).not.toBe('token'); + expect(logged.buffer).toBe('[content]'); + expect(logged.content).toBe('[content]'); + expect(logged.prBody).toBe('[Template]'); + expect(logged.secrets.foo).toBe('***********'); + expect(logged.someFn).toBe('[function]'); + expect(logged.someObject.field).toBe('**redacted**'); }); }); diff --git a/lib/logger/index.ts b/lib/logger/index.ts index 421f86caa35d1c..66f91378fdc236 100644 --- a/lib/logger/index.ts +++ b/lib/logger/index.ts @@ -1,6 +1,6 @@ import is from '@sindresorhus/is'; import * as bunyan from 'bunyan'; -import * as shortid from 'shortid'; +import { nanoid } from 'nanoid'; import cmdSerializer from './cmd-serializer'; import configSerializer from './config-serializer'; import errSerializer from './err-serializer'; @@ -8,8 +8,8 @@ import { RenovateStream } from './pretty-stdout'; import type { BunyanRecord, Logger } from './types'; import { ProblemStream, withSanitizer } from './utils'; -let logContext: string = process.env.LOG_CONTEXT || shortid.generate(); -let curMeta = {}; +let logContext: string = process.env.LOG_CONTEXT ?? nanoid(); +let curMeta: Record = {}; const problems = new ProblemStream(); diff --git a/lib/logger/pretty-stdout.ts b/lib/logger/pretty-stdout.ts index fc97db214cf8ae..2259b880177b5b 100644 --- a/lib/logger/pretty-stdout.ts +++ b/lib/logger/pretty-stdout.ts @@ -37,7 +37,7 @@ const levels: Record = { export function indent(str: string, leading = false): string { const prefix = leading ? ' ' : ''; - return prefix + str.split(/\r?\n/).join('\n '); // TODO #12070 + return prefix + str.split(/\r?\n/).join('\n '); // TODO #12874 } export function getMeta(rec: BunyanRecord): string { diff --git a/lib/logger/types.ts b/lib/logger/types.ts index a7d081e0a28055..a4fa07fe2da407 100644 --- a/lib/logger/types.ts +++ b/lib/logger/types.ts @@ -30,5 +30,9 @@ export interface BunyanRecord extends Record { export type BunyanStream = (NodeJS.WritableStream | Stream) & { writable?: boolean; - write: (chunk: BunyanRecord, enc, cb) => void; + write: ( + chunk: BunyanRecord, + enc: BufferEncoding, + cb: (err?: Error | null) => void + ) => void; }; diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index 0f9abe39c50d36..cb0de0a395ba7d 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -1,4 +1,5 @@ import { Stream } from 'stream'; +import is from '@sindresorhus/is'; import bunyan from 'bunyan'; import fs from 'fs-extra'; import { clone } from '../util/clone'; @@ -70,16 +71,18 @@ export default function prepareError(err: Error): Record { }; response.options = options; - for (const k of ['username', 'password', 'method', 'http2']) { - options[k] = err.options[k]; - } + options.username = err.options.username; + options.password = err.options.password; + options.method = err.options.method; + options.http2 = err.options.http2; // istanbul ignore else if (err.response) { response.response = { statusCode: err.response?.statusCode, statusMessage: err.response?.statusMessage, - body: clone(err.response.body), + body: + err.name === 'TimeoutError' ? undefined : clone(err.response.body), headers: clone(err.response.headers), httpVersion: err.response.httpVersion, }; @@ -89,38 +92,54 @@ export default function prepareError(err: Error): Record { return response; } -export function sanitizeValue(value: unknown, seen = new WeakMap()): any { - if (Array.isArray(value)) { - const length = value.length; - const arrayResult = Array(length); - seen.set(value, arrayResult); - for (let idx = 0; idx < length; idx += 1) { - const val = value[idx]; - arrayResult[idx] = seen.has(val) - ? seen.get(val) - : sanitizeValue(val, seen); - } - return arrayResult; +type NestedValue = unknown[] | object; + +function isNested(value: unknown): value is NestedValue { + return is.array(value) || is.object(value); +} + +export function sanitizeValue( + value: unknown, + seen = new WeakMap() +): any { + if (is.string(value)) { + return sanitize(value); } - if (value instanceof Buffer) { - return '[content]'; + if (is.date(value)) { + return value; } - if (value instanceof Error) { - // eslint-disable-next-line no-param-reassign - value = prepareError(value); + if (is.function_(value)) { + return '[function]'; } - const valueType = typeof value; + if (is.buffer(value)) { + return '[content]'; + } + + if (is.error(value)) { + const err = prepareError(value); + return sanitizeValue(err, seen); + } - if (value != null && valueType !== 'function' && valueType === 'object') { - if (value instanceof Date) { - return value; + if (is.array(value)) { + const length = value.length; + const arrayResult = Array(length); + seen.set(value, arrayResult); + for (let idx = 0; idx < length; idx += 1) { + const val = value[idx]; + arrayResult[idx] = + isNested(val) && seen.has(val) + ? seen.get(val) + : sanitizeValue(val, seen); } + return arrayResult; + } + if (is.object(value)) { const objectResult: Record = {}; - seen.set(value as any, objectResult); + seen.set(value, objectResult); for (const [key, val] of Object.entries(value)) { let curValue: any; if (!val) { @@ -142,10 +161,11 @@ export function sanitizeValue(value: unknown, seen = new WeakMap()): any { objectResult[key] = curValue; } + return objectResult; } - return valueType === 'string' ? sanitize(value as string) : value; + return value; } export function withSanitizer(streamConfig: bunyan.Stream): bunyan.Stream { @@ -155,12 +175,16 @@ export function withSanitizer(streamConfig: bunyan.Stream): bunyan.Stream { const stream = streamConfig.stream as BunyanStream; if (stream?.writable) { - const write = (chunk: BunyanRecord, enc, cb): void => { + const write = ( + chunk: BunyanRecord, + enc: BufferEncoding, + cb: (err?: Error | null) => void + ): void => { const raw = sanitizeValue(chunk); const result = streamConfig.type === 'raw' ? raw - : JSON.stringify(raw, bunyan.safeCycles()).replace(/\n?$/, '\n'); // TODO #12070 + : JSON.stringify(raw, bunyan.safeCycles()).replace(/\n?$/, '\n'); // TODO #12874 stream.write(result, enc, cb); }; diff --git a/lib/manager/ansible-galaxy/collections.ts b/lib/manager/ansible-galaxy/collections.ts index adf6f4cd65697c..43888baf49a9b4 100644 --- a/lib/manager/ansible-galaxy/collections.ts +++ b/lib/manager/ansible-galaxy/collections.ts @@ -49,7 +49,6 @@ function handleGitDep( dep: PackageDependency, nameMatch: RegExpExecArray ): void { - /* eslint-disable no-param-reassign */ dep.datasource = GitTagsDatasource.id; if (nameMatch) { @@ -76,16 +75,13 @@ function handleGitDep( dep.currentValue = dep.managerData.version; } } - /* eslint-enable no-param-reassign */ } function handleGalaxyDep(dep: PackageDependency): void { - /* eslint-disable no-param-reassign */ dep.datasource = GalaxyCollectionDatasource.id; dep.depName = dep.managerData.name; dep.registryUrls = dep.managerData.source ? [dep.managerData.source] : []; dep.currentValue = dep.managerData.version; - /* eslint-enable no-param-reassign */ } function finalize(dependency: PackageDependency): boolean { @@ -124,7 +120,7 @@ function finalize(dependency: PackageDependency): boolean { return true; } - if (dependency.currentValue == null && dep.skipReason == null) { + if (!dependency.currentValue && !dep.skipReason) { dep.skipReason = SkipReason.NoVersion; } return true; diff --git a/lib/manager/ansible-galaxy/extract.spec.ts b/lib/manager/ansible-galaxy/extract.spec.ts index 960e6f8ea3cb25..1e2b80d0a62666 100644 --- a/lib/manager/ansible-galaxy/extract.spec.ts +++ b/lib/manager/ansible-galaxy/extract.spec.ts @@ -35,9 +35,7 @@ describe('manager/ansible-galaxy/extract', () => { const res = extractPackageFile(collections1, 'requirements.yml'); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(13); - expect(res.deps.filter((value) => value.skipReason != null)).toHaveLength( - 6 - ); + expect(res.deps.filter((value) => value.skipReason)).toHaveLength(6); }); it('check collection style requirements file in reverse order and missing empty line', () => { const res = extractPackageFile(collections2, 'requirements.yml'); diff --git a/lib/manager/ansible-galaxy/roles.ts b/lib/manager/ansible-galaxy/roles.ts index f47da5a9059998..23aa4398147351 100644 --- a/lib/manager/ansible-galaxy/roles.ts +++ b/lib/manager/ansible-galaxy/roles.ts @@ -94,7 +94,7 @@ export function extractRoles(lines: string[]): PackageDependency[] { }; do { const localdep = interpretLine(lineMatch, lineNumber, dep); - if (localdep == null) { + if (!localdep) { break; } const line = lines[lineNumber + 1]; diff --git a/lib/manager/api.ts b/lib/manager/api.ts index 1731c2500f3fab..de7265d558fd4f 100644 --- a/lib/manager/api.ts +++ b/lib/manager/api.ts @@ -34,6 +34,7 @@ import * as helmv3 from './helmv3'; import * as homebrew from './homebrew'; import * as html from './html'; import * as jenkins from './jenkins'; +import * as jsonnetBundler from './jsonnet-bundler'; import * as kubernetes from './kubernetes'; import * as kustomize from './kustomize'; import * as leiningen from './leiningen'; @@ -103,6 +104,7 @@ api.set('helmv3', helmv3); api.set('homebrew', homebrew); api.set('html', html); api.set('jenkins', jenkins); +api.set('jsonnet-bundler', jsonnetBundler); api.set('kubernetes', kubernetes); api.set('kustomize', kustomize); api.set('leiningen', leiningen); diff --git a/lib/manager/argocd/extract.ts b/lib/manager/argocd/extract.ts index ae5d1dd730d36b..b4b0839fc2260f 100644 --- a/lib/manager/argocd/extract.ts +++ b/lib/manager/argocd/extract.ts @@ -9,10 +9,10 @@ import { fileTestRegex } from './util'; function createDependency( definition: ApplicationDefinition ): PackageDependency { - const source = definition.spec?.source; + const source = definition?.spec?.source; if ( - source == null || + !source || !is.nonEmptyString(source.repoURL) || !is.nonEmptyString(source.targetRevision) ) { diff --git a/lib/manager/batect/extract.spec.ts b/lib/manager/batect/extract.spec.ts index 8b76e27da3ae99..dcda4ee57289b8 100644 --- a/lib/manager/batect/extract.spec.ts +++ b/lib/manager/batect/extract.spec.ts @@ -1,4 +1,4 @@ -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { id as dockerVersioning } from '../../versioning/docker'; @@ -35,11 +35,11 @@ const config: ExtractConfig = {}; describe('manager/batect/extract', () => { describe('extractPackageFile()', () => { beforeEach(() => { - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns empty array for empty configuration file', async () => { diff --git a/lib/manager/bazel/extract.ts b/lib/manager/bazel/extract.ts index 123f54d75fd976..1e97dbffff4566 100644 --- a/lib/manager/bazel/extract.ts +++ b/lib/manager/bazel/extract.ts @@ -51,7 +51,7 @@ function parseUrl(urlString: string): UrlParsedResult | null { const lexer = moo.states({ main: { - lineComment: { match: /#.*?$/ }, // TODO #12070 + lineComment: { match: /#.*?$/ }, // TODO #12870 leftParen: { match: '(' }, rightParen: { match: ')' }, longDoubleQuoted: { diff --git a/lib/manager/bundler/__snapshots__/extract.spec.ts.snap b/lib/manager/bundler/__snapshots__/extract.spec.ts.snap index 5a50bac31a485b..1c0f4aee0fb2fe 100644 --- a/lib/manager/bundler/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/bundler/__snapshots__/extract.spec.ts.snap @@ -16,20 +16,20 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "puma", "lockedVersion": "4.3.1", "managerData": Object { "lineNumber": 5, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "bootsnap", "lockedVersion": "1.4.5", "managerData": Object { "lineNumber": 6, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 5.0", @@ -41,30 +41,31 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "sass-rails-bootstrap", "lockedVersion": "2.2.2.3", "managerData": Object { "lineNumber": 9, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "jquery-rails", "lockedVersion": "4.3.5", "managerData": Object { "lineNumber": 10, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "uglifier", "lockedVersion": "4.2.0", "managerData": Object { "lineNumber": 11, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "foreman", "depTypes": Array [ "development", @@ -73,9 +74,9 @@ Object { "managerData": Object { "lineNumber": 14, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "sqlite3", "depTypes": Array [ "development", @@ -84,9 +85,9 @@ Object { "managerData": Object { "lineNumber": 15, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "listen", "depTypes": Array [ "development", @@ -95,9 +96,9 @@ Object { "managerData": Object { "lineNumber": 16, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "pg", "depTypes": Array [ "production", @@ -106,9 +107,9 @@ Object { "managerData": Object { "lineNumber": 20, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "newrelic_rpm", "depTypes": Array [ "production", @@ -117,7 +118,6 @@ Object { "managerData": Object { "lineNumber": 21, }, - "skipReason": "no-version", }, Object { "currentValue": "< 1.17.2", @@ -132,6 +132,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "airbrake", "depTypes": Array [ "production", @@ -140,7 +141,6 @@ Object { "managerData": Object { "lineNumber": 23, }, - "skipReason": "no-version", }, ], "lockFiles": Array [ @@ -339,12 +339,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "browser", "lockedVersion": "2.7.1", "managerData": Object { "lineNumber": 29, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 0.7.7", @@ -356,12 +356,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "iso-639", "lockedVersion": "0.2.8", "managerData": Object { "lineNumber": 31, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 5.1", @@ -476,12 +476,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "fastimage", "lockedVersion": "2.1.7", "managerData": Object { "lineNumber": 49, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 2.1", @@ -511,11 +511,11 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "health_check", "managerData": Object { "lineNumber": 53, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 4.3", @@ -562,12 +562,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "idn-ruby", "lockedVersion": "0.1.0", "managerData": Object { "lineNumber": 59, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.1", @@ -597,11 +597,11 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "nilsimsa", "managerData": Object { "lineNumber": 63, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.10", @@ -649,12 +649,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "parslet", "lockedVersion": "1.8.2", "managerData": Object { "lineNumber": 69, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.19", @@ -666,11 +666,11 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "posix-spawn", "managerData": Object { "lineNumber": 71, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 2.1", @@ -682,12 +682,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "premailer-rails", "lockedVersion": "1.10.3", "managerData": Object { "lineNumber": 73, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 6.2", @@ -897,19 +897,19 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "webpush", "lockedVersion": "0.3.8", "managerData": Object { "lineNumber": 97, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "json-ld", "managerData": Object { "lineNumber": 99, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 3.0", @@ -1213,6 +1213,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "memory_profiler", "depTypes": Array [ "development", @@ -1221,7 +1222,6 @@ Object { "managerData": Object { "lineNumber": 136, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 0.78", @@ -1320,6 +1320,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "derailed_benchmarks", "depTypes": Array [ "development", @@ -1328,9 +1329,9 @@ Object { "managerData": Object { "lineNumber": 147, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "stackprof", "depTypes": Array [ "development", @@ -1339,7 +1340,6 @@ Object { "managerData": Object { "lineNumber": 148, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 0.11", @@ -1366,20 +1366,20 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "concurrent-ruby", "lockedVersion": "1.1.5", "managerData": Object { "lineNumber": 156, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "connection_pool", "lockedVersion": "2.2.2", "managerData": Object { "lineNumber": 157, }, - "skipReason": "no-version", }, ], "lockFiles": Array [ @@ -1398,12 +1398,12 @@ Object { }, "deps": Array [ Object { + "datasource": "rubygems", "depName": "rails", "lockedVersion": "6.0.1", "managerData": Object { "lineNumber": 4, }, - "skipReason": "no-version", }, Object { "currentValue": ">= 11.1", @@ -1415,12 +1415,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "rack-proxy", "lockedVersion": "0.6.5", "managerData": Object { "lineNumber": 6, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 5.0", @@ -1435,6 +1435,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "byebug", "depTypes": Array [ "test", @@ -1443,7 +1444,6 @@ Object { "managerData": Object { "lineNumber": 10, }, - "skipReason": "no-version", }, ], "lockFiles": Array [ @@ -1489,12 +1489,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "sass-rails", "lockedVersion": "5.0.7", "managerData": Object { "lineNumber": 14, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 5", @@ -1506,11 +1506,11 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "webpacker", "managerData": Object { "lineNumber": 16, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 3.1.11", @@ -1573,6 +1573,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "w3c_validators", "depTypes": Array [ "doc", @@ -1581,7 +1582,6 @@ Object { "managerData": Object { "lineNumber": 34, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.2.0", @@ -1596,12 +1596,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "dalli", "lockedVersion": "2.7.9", "managerData": Object { "lineNumber": 39, }, - "skipReason": "no-version", }, Object { "currentValue": "\\">= 3.0.5\\", \\"< 3.2\\"", @@ -1613,20 +1613,20 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "libxml-ruby", "lockedVersion": "3.1.0", "managerData": Object { "lineNumber": 41, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "connection_pool", "lockedVersion": "2.2.2", "managerData": Object { "lineNumber": 42, }, - "skipReason": "no-version", }, Object { "currentValue": ">= 1.1.0", @@ -1638,6 +1638,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "resque", "depTypes": Array [ "job", @@ -1646,9 +1647,9 @@ Object { "managerData": Object { "lineNumber": 49, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "resque-scheduler", "depTypes": Array [ "job", @@ -1657,9 +1658,9 @@ Object { "managerData": Object { "lineNumber": 50, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "sidekiq", "depTypes": Array [ "job", @@ -1668,9 +1669,9 @@ Object { "managerData": Object { "lineNumber": 51, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "sucker_punch", "depTypes": Array [ "job", @@ -1679,9 +1680,9 @@ Object { "managerData": Object { "lineNumber": 52, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "delayed_job", "depTypes": Array [ "job", @@ -1690,9 +1691,9 @@ Object { "managerData": Object { "lineNumber": 53, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "queue_classic", "depTypes": Array [ "job", @@ -1700,9 +1701,9 @@ Object { "managerData": Object { "lineNumber": 54, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "sneakers", "depTypes": Array [ "job", @@ -1711,9 +1712,9 @@ Object { "managerData": Object { "lineNumber": 55, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "que", "depTypes": Array [ "job", @@ -1722,9 +1723,9 @@ Object { "managerData": Object { "lineNumber": 56, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "backburner", "depTypes": Array [ "job", @@ -1733,9 +1734,9 @@ Object { "managerData": Object { "lineNumber": 57, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "delayed_job_active_record", "depTypes": Array [ "job", @@ -1744,9 +1745,9 @@ Object { "managerData": Object { "lineNumber": 58, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "sequel", "depTypes": Array [ "job", @@ -1755,9 +1756,9 @@ Object { "managerData": Object { "lineNumber": 59, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "puma", "depTypes": Array [ "cable", @@ -1766,9 +1767,9 @@ Object { "managerData": Object { "lineNumber": 64, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "hiredis", "depTypes": Array [ "cable", @@ -1777,7 +1778,6 @@ Object { "managerData": Object { "lineNumber": 66, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 4.0", @@ -1792,6 +1792,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "redis-namespace", "depTypes": Array [ "cable", @@ -1800,9 +1801,9 @@ Object { "managerData": Object { "lineNumber": 69, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "websocket-client-simple", "depTypes": Array [ "cable", @@ -1810,9 +1811,9 @@ Object { "managerData": Object { "lineNumber": 71, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "blade", "depTypes": Array [ "cable", @@ -1821,9 +1822,9 @@ Object { "managerData": Object { "lineNumber": 73, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "blade-sauce_labs_plugin", "depTypes": Array [ "cable", @@ -1832,9 +1833,9 @@ Object { "managerData": Object { "lineNumber": 74, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "sprockets-export", "depTypes": Array [ "cable", @@ -1843,9 +1844,9 @@ Object { "managerData": Object { "lineNumber": 75, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "aws-sdk-s3", "depTypes": Array [ "storage", @@ -1854,7 +1855,6 @@ Object { "managerData": Object { "lineNumber": 80, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.11", @@ -1869,6 +1869,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "azure-storage", "depTypes": Array [ "storage", @@ -1877,7 +1878,6 @@ Object { "managerData": Object { "lineNumber": 82, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.2", @@ -1892,22 +1892,23 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "aws-sdk-sns", "lockedVersion": "1.8.1", "managerData": Object { "lineNumber": 88, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "webmock", "lockedVersion": "3.4.2", "managerData": Object { "lineNumber": 89, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "qunit-selenium", "depTypes": Array [ "ujs", @@ -1916,9 +1917,9 @@ Object { "managerData": Object { "lineNumber": 92, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "chromedriver-helper", "depTypes": Array [ "ujs", @@ -1927,9 +1928,9 @@ Object { "managerData": Object { "lineNumber": 93, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "minitest-bisect", "depTypes": Array [ "test", @@ -1938,9 +1939,9 @@ Object { "managerData": Object { "lineNumber": 101, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "minitest-retry", "depTypes": Array [ "test", @@ -1949,9 +1950,9 @@ Object { "managerData": Object { "lineNumber": 102, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "stackprof", "depTypes": Array [ "test", @@ -1960,9 +1961,9 @@ Object { "managerData": Object { "lineNumber": 105, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "byebug", "depTypes": Array [ "test", @@ -1971,9 +1972,9 @@ Object { "managerData": Object { "lineNumber": 106, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "benchmark-ips", "depTypes": Array [ "test", @@ -1982,7 +1983,6 @@ Object { "managerData": Object { "lineNumber": 109, }, - "skipReason": "no-version", }, Object { "currentValue": ">= 1.8.1", @@ -2036,14 +2036,15 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "activerecord-jdbcsqlite3-adapter", "lockedVersion": "52.1-java", "managerData": Object { "lineNumber": 129, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "activerecord-jdbcmysql-adapter", "depTypes": Array [ "db", @@ -2052,9 +2053,9 @@ Object { "managerData": Object { "lineNumber": 131, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "activerecord-jdbcpostgresql-adapter", "depTypes": Array [ "db", @@ -2063,7 +2064,6 @@ Object { "managerData": Object { "lineNumber": 132, }, - "skipReason": "no-version", }, Object { "currentValue": ">= 1.3.0", @@ -2116,26 +2116,26 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "activerecord-oracle_enhanced-adapter", "managerData": Object { "lineNumber": 154, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "ibm_db", "managerData": Object { "lineNumber": 158, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "tzinfo-data", "lockedVersion": "1.2018.7", "managerData": Object { "lineNumber": 159, }, - "skipReason": "no-version", }, Object { "currentValue": ">= 0.1.0", @@ -2163,6 +2163,7 @@ Object { }, "deps": Array [ Object { + "datasource": "rubygems", "depName": "some_internal_gem", "managerData": Object { "lineNumber": 4, @@ -2170,9 +2171,9 @@ Object { "registryUrls": Array [ "https://gems.example.com", ], - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "another_internal_gem", "managerData": Object { "lineNumber": 5, @@ -2180,7 +2181,6 @@ Object { "registryUrls": Array [ "https://gems.example.com", ], - "skipReason": "no-version", }, Object { "currentValue": "latest", @@ -2191,13 +2191,14 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "sqlite3", "managerData": Object { "lineNumber": 10, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "wirble", "depTypes": Array [ "development", @@ -2206,9 +2207,9 @@ Object { "managerData": Object { "lineNumber": 14, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "faker", "depTypes": Array [ "development", @@ -2217,7 +2218,6 @@ Object { "managerData": Object { "lineNumber": 15, }, - "skipReason": "no-version", }, ], "registryUrls": Array [ @@ -2539,12 +2539,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "gssapi", "lockedVersion": "1.2.0", "managerData": Object { "lineNumber": 52, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 4.11", @@ -2664,12 +2664,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "net-ldap", "lockedVersion": "0.16.0", "managerData": Object { "lineNumber": 81, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.1.0", @@ -2735,12 +2735,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "hashie-forbidden_attributes", "lockedVersion": "0.1.1", "managerData": Object { "lineNumber": 98, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.0", @@ -2770,12 +2770,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "mini_magick", "lockedVersion": "4.9.5", "managerData": Object { "lineNumber": 108, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 3.5", @@ -2895,20 +2895,20 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "aws-sdk", "lockedVersion": "2.11.374", "managerData": Object { "lineNumber": 134, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "faraday_middleware-aws-signers-v4", "lockedVersion": "0.1.7", "managerData": Object { "lineNumber": 135, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 2.12", @@ -3073,12 +3073,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "icalendar", "lockedVersion": "2.4.1", "managerData": Object { "lineNumber": 158, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 3.1.0", @@ -3156,6 +3156,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "rack-timeout", "depTypes": Array [ "puma", @@ -3164,7 +3165,6 @@ Object { "managerData": Object { "lineNumber": 175, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 0.6.0", @@ -3248,12 +3248,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "ruby-progressbar", "lockedVersion": "1.10.1", "managerData": Object { "lineNumber": 200, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 2.0.9", @@ -3292,12 +3292,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "device_detector", "lockedVersion": "1.0.0", "managerData": Object { "lineNumber": 216, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 4.0", @@ -3480,12 +3480,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "fast_blank", "lockedVersion": "1.0.0", "managerData": Object { "lineNumber": 273, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 0.10.5", @@ -3713,12 +3713,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "derailed_benchmarks", "lockedVersion": "1.3.5", "managerData": Object { "lineNumber": 321, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 0.8", @@ -3892,6 +3892,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "awesome_print", "depTypes": Array [ "development", @@ -3901,7 +3902,6 @@ Object { "managerData": Object { "lineNumber": 353, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.7.0", @@ -4252,6 +4252,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "rspec-parameterized", "depTypes": Array [ "test", @@ -4260,7 +4261,6 @@ Object { "managerData": Object { "lineNumber": 404, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 3.22.0", @@ -4347,6 +4347,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "rails-controller-testing", "depTypes": Array [ "test", @@ -4355,7 +4356,6 @@ Object { "managerData": Object { "lineNumber": 414, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.1", @@ -4382,6 +4382,7 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "rspec_junit_formatter", "depTypes": Array [ "test", @@ -4390,9 +4391,9 @@ Object { "managerData": Object { "lineNumber": 417, }, - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "guard-rspec", "depTypes": Array [ "test", @@ -4401,7 +4402,6 @@ Object { "managerData": Object { "lineNumber": 418, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 4.9", @@ -4431,12 +4431,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "html2text", "lockedVersion": "0.2.0", "managerData": Object { "lineNumber": 426, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 1.0.0", @@ -4520,12 +4520,12 @@ Object { }, }, Object { + "datasource": "rubygems", "depName": "net-ntp", "lockedVersion": "2.1.3", "managerData": Object { "lineNumber": 445, }, - "skipReason": "no-version", }, Object { "currentValue": "~> 5.2", @@ -4745,6 +4745,7 @@ Object { }, "deps": Array [ Object { + "datasource": "rubygems", "depName": "rubocop", "lockedVersion": "0.68.1", "managerData": Object { @@ -4753,9 +4754,9 @@ Object { "registryUrls": Array [ "https://rubygems.org", ], - "skipReason": "no-version", }, Object { + "datasource": "rubygems", "depName": "brakeman", "lockedVersion": "4.4.0", "managerData": Object { @@ -4764,7 +4765,6 @@ Object { "registryUrls": Array [ "https://rubygems.org", ], - "skipReason": "no-version", }, ], "lockFiles": Array [ diff --git a/lib/manager/bundler/artifacts.spec.ts b/lib/manager/bundler/artifacts.spec.ts index a13202422dec75..d0ce943b323a93 100644 --- a/lib/manager/bundler/artifacts.spec.ts +++ b/lib/manager/bundler/artifacts.spec.ts @@ -2,12 +2,12 @@ import { exec as _exec } from 'child_process'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { fs, git, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as _datasource from '../../datasource'; import * as docker from '../../util/exec/docker'; import * as _env from '../../util/exec/env'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import type { UpdateArtifactsConfig } from '../types'; import * as _bundlerHostRules from './host-rules'; import { updateArtifacts } from '.'; @@ -52,11 +52,11 @@ describe('manager/bundler/artifacts', () => { bundlerHostRules.findAllAuthenticatable.mockReturnValue([]); docker.resetPrefetchedImages(); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); fs.ensureCacheDir.mockResolvedValue('/tmp/cache/others/gem'); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null by default', async () => { expect( @@ -106,7 +106,7 @@ describe('manager/bundler/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('works explicit global binarySource', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'global' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce(null); @@ -127,7 +127,7 @@ describe('manager/bundler/artifacts', () => { }); describe('Docker', () => { beforeEach(() => { - setGlobalConfig({ + GlobalConfig.set({ ...adminConfig, binarySource: 'docker', }); @@ -159,7 +159,7 @@ describe('manager/bundler/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('constraints options', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); datasource.getPkgReleases.mockResolvedValueOnce({ @@ -191,7 +191,7 @@ describe('manager/bundler/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('invalid constraints options', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); datasource.getPkgReleases.mockResolvedValueOnce({ @@ -224,7 +224,7 @@ describe('manager/bundler/artifacts', () => { }); it('injects bundler host configuration environment variables', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); @@ -264,7 +264,7 @@ describe('manager/bundler/artifacts', () => { }); it('injects bundler host configuration as command with bundler < 2', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); @@ -309,7 +309,7 @@ describe('manager/bundler/artifacts', () => { }); it('injects bundler host configuration as command with bundler >= 2', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); @@ -354,7 +354,7 @@ describe('manager/bundler/artifacts', () => { }); it('injects bundler host configuration as command with bundler == latest', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); diff --git a/lib/manager/bundler/artifacts.ts b/lib/manager/bundler/artifacts.ts index f4d4860d0d3aa7..a69d7389e9b8ba 100644 --- a/lib/manager/bundler/artifacts.ts +++ b/lib/manager/bundler/artifacts.ts @@ -7,7 +7,8 @@ import { import { logger } from '../../logger'; import { HostRule } from '../../types'; import * as memCache from '../../util/cache/memory'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { deleteLocalFile, ensureCacheDir, @@ -183,8 +184,8 @@ export async function updateArtifacts( image: 'ruby', tagScheme: 'ruby', tagConstraint: await getRubyConstraint(updateArtifact), - preCommands, }, + preCommands, }; await exec(cmd, execOptions); diff --git a/lib/manager/bundler/extract.ts b/lib/manager/bundler/extract.ts index 19b5fe5c5d0508..2d8ccd655cc317 100644 --- a/lib/manager/bundler/extract.ts +++ b/lib/manager/bundler/extract.ts @@ -1,6 +1,5 @@ -import * as datasourceRubygems from '../../datasource/rubygems'; +import { RubyGemsDatasource } from '../../datasource/rubygems'; import { logger } from '../../logger'; -import { SkipReason } from '../../types'; import { readLocalFile } from '../../util/fs'; import { regEx } from '../../util/regex'; import type { PackageDependency, PackageFile } from '../types'; @@ -39,8 +38,9 @@ export async function extractPackageFile( if (rubyMatch) { res.constraints = { ruby: rubyMatch[1] }; } - const gemMatchRegex = - /^\s*gem\s+(['"])(?[^'"]+)\1(\s*,\s*(?(['"])[^'"]+\5(\s*,\s*\5[^'"]+\5)?))?/; // TODO #12070 #12071 + const gemMatchRegex = regEx( + `^\\s*gem\\s+(['"])(?[^'"]+)(['"])(\\s*,\\s*(?(['"])[^'"]+['"](\\s*,\\s*['"][^'"]+['"])?))?` + ); // TODO #12071 const gemMatch = gemMatchRegex.exec(line); if (gemMatch) { const dep: PackageDependency = { @@ -52,12 +52,8 @@ export async function extractPackageFile( dep.currentValue = regEx(/\s*,\s*/).test(currentValue) // TODO #12071 ? currentValue : currentValue.slice(1, -1); - } else { - dep.skipReason = SkipReason.NoVersion; - } - if (!dep.skipReason) { - dep.datasource = datasourceRubygems.id; } + dep.datasource = RubyGemsDatasource.id; res.deps.push(dep); } const groupMatch = regEx(/^group\s+(.*?)\s+do/).exec(line); // TODO #12071 @@ -141,7 +137,6 @@ export async function extractPackageFile( const platformsRes = await extractPackageFile(platformsContent); if (platformsRes) { res.deps = res.deps.concat( - // eslint-disable-next-line no-loop-func platformsRes.deps.map((dep) => ({ ...dep, managerData: { @@ -167,7 +162,6 @@ export async function extractPackageFile( const ifRes = await extractPackageFile(ifContent); if (ifRes) { res.deps = res.deps.concat( - // eslint-disable-next-line no-loop-func ifRes.deps.map((dep) => ({ ...dep, managerData: { diff --git a/lib/manager/bundler/gemfile.spec.ts b/lib/manager/bundler/gemfile.spec.ts index 6d6dfc8ec4ae29..849a921e9d3890 100644 --- a/lib/manager/bundler/gemfile.spec.ts +++ b/lib/manager/bundler/gemfile.spec.ts @@ -6,7 +6,7 @@ const gemLockFile = loadFixture('Gemfile.rails.lock'); describe('manager/bundler/gemfile', () => { it('matches the expected output', () => { const res = extractLockFileEntries(gemLockFile); - expect(res.size).toEqual(185); + expect(res.size).toBe(185); expect(res).toMatchSnapshot(); }); }); diff --git a/lib/manager/bundler/host-rules.spec.ts b/lib/manager/bundler/host-rules.spec.ts index 3a8c85b01a4fce..faccd49560f227 100644 --- a/lib/manager/bundler/host-rules.spec.ts +++ b/lib/manager/bundler/host-rules.spec.ts @@ -17,14 +17,14 @@ describe('manager/bundler/host-rules', () => { username: 'test', password: 'password', }) - ).toEqual('test:password'); + ).toBe('test:password'); }); it('returns the authentication header with the token', () => { expect( getAuthenticationHeaderValue({ token: 'token', }) - ).toEqual('token'); + ).toBe('token'); }); }); describe('findAllAuthenticatable()', () => { diff --git a/lib/manager/bundler/locked-version.spec.ts b/lib/manager/bundler/locked-version.spec.ts index 6921b83bd28000..8110d6cdab59ff 100644 --- a/lib/manager/bundler/locked-version.spec.ts +++ b/lib/manager/bundler/locked-version.spec.ts @@ -10,27 +10,27 @@ const gitlabFossGemfileLock = loadFixture('Gemfile.gitlab-foss.lock'); describe('manager/bundler/locked-version', () => { test('Parse Rails Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(railsGemfileLock); - expect(parsedLockEntries.size).toEqual(185); + expect(parsedLockEntries.size).toBe(185); expect(parsedLockEntries).toMatchSnapshot(); }); test('Parse WebPacker Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(webPackerGemfileLock); - expect(parsedLockEntries.size).toEqual(53); + expect(parsedLockEntries.size).toBe(53); expect(parsedLockEntries).toMatchSnapshot(); }); test('Parse Mastodon Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(mastodonGemfileLock); - expect(parsedLockEntries.size).toEqual(266); + expect(parsedLockEntries.size).toBe(266); expect(parsedLockEntries).toMatchSnapshot(); }); test('Parse Ruby CI Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(rubyCIGemfileLock); - expect(parsedLockEntries.size).toEqual(64); + expect(parsedLockEntries.size).toBe(64); expect(parsedLockEntries).toMatchSnapshot(); }); test('Parse Gitlab Foss Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(gitlabFossGemfileLock); - expect(parsedLockEntries.size).toEqual(478); + expect(parsedLockEntries.size).toBe(478); expect(parsedLockEntries).toMatchSnapshot(); }); }); diff --git a/lib/manager/bundler/locked-version.ts b/lib/manager/bundler/locked-version.ts index c18ceb244d2209..e2d786c9e0b966 100644 --- a/lib/manager/bundler/locked-version.ts +++ b/lib/manager/bundler/locked-version.ts @@ -1,7 +1,7 @@ import { logger } from '../../logger'; import { isVersion } from '../../versioning/ruby'; -const DEP_REGEX = new RegExp('(?<=\\().*(?=\\))'); // TODO #12070 +const DEP_REGEX = new RegExp('(?<=\\().*(?=\\))'); // TODO #12872 (?<=re) after text matching export function extractLockFileEntries( lockFileContent: string ): Map { diff --git a/lib/manager/bundler/range.spec.ts b/lib/manager/bundler/range.spec.ts index 7591971aa8d10e..3755b379fd59cc 100644 --- a/lib/manager/bundler/range.spec.ts +++ b/lib/manager/bundler/range.spec.ts @@ -5,11 +5,11 @@ describe('manager/bundler/range', () => { describe('getRangeStrategy()', () => { it('returns replace when rangeStrategy is auto', () => { const config: RangeConfig = { rangeStrategy: 'auto' }; - expect(getRangeStrategy(config)).toEqual('replace'); + expect(getRangeStrategy(config)).toBe('replace'); }); it('returns the config value when rangeStrategy is different than auto', () => { const config: RangeConfig = { rangeStrategy: 'update-lockfile' }; - expect(getRangeStrategy(config)).toEqual('update-lockfile'); + expect(getRangeStrategy(config)).toBe('update-lockfile'); }); }); }); diff --git a/lib/manager/cake/index.ts b/lib/manager/cake/index.ts index c469a3e29f644b..5d2a05166c602d 100644 --- a/lib/manager/cake/index.ts +++ b/lib/manager/cake/index.ts @@ -13,13 +13,13 @@ export const defaultConfig = { const lexer = moo.states({ main: { - lineComment: { match: /\/\/.*?$/ }, // TODO #12070 - multiLineComment: { match: /\/\*[^]*?\*\//, lineBreaks: true }, // TODO #12070 + lineComment: { match: /\/\/.*?$/ }, // TODO #12870 + multiLineComment: { match: /\/\*[^]*?\*\//, lineBreaks: true }, // TODO #12870 dependency: { - match: /^#(?:addin|tool|module|load|l)\s+(?:nuget|dotnet):.*$/, // TODO #12070 + match: /^#(?:addin|tool|module|load|l)\s+(?:nuget|dotnet):.*$/, // TODO #12870 }, dependencyQuoted: { - match: /^#(?:addin|tool|module|load|l)\s+"(?:nuget|dotnet):[^"]+"\s*$/, // TODO #12070 + match: /^#(?:addin|tool|module|load|l)\s+"(?:nuget|dotnet):[^"]+"\s*$/, // TODO #12870 value: (s: string) => s.trim().slice(1, -1), }, unknown: moo.fallback, diff --git a/lib/manager/cargo/artifacts.spec.ts b/lib/manager/cargo/artifacts.spec.ts index b3ee807645ec01..aa867d7a872957 100644 --- a/lib/manager/cargo/artifacts.spec.ts +++ b/lib/manager/cargo/artifacts.spec.ts @@ -3,7 +3,7 @@ import _fs from 'fs-extra'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { git, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as docker from '../../util/exec/docker'; import * as _env from '../../util/exec/env'; @@ -33,11 +33,11 @@ describe('manager/cargo/artifacts', () => { jest.resetModules(); env.getChildProcessEnv.mockReturnValue(envMock.basic); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null if no Cargo.lock found', async () => { fs.stat.mockRejectedValue(new Error('not found!')); @@ -171,7 +171,7 @@ describe('manager/cargo/artifacts', () => { it('returns updated Cargo.lock with docker', async () => { fs.stat.mockResolvedValueOnce({ name: 'Cargo.lock' } as any); - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); git.getFile.mockResolvedValueOnce('Old Cargo.lock'); const execSnapshots = mockExecAll(exec); fs.readFile.mockResolvedValueOnce('New Cargo.lock' as any); diff --git a/lib/manager/cargo/artifacts.ts b/lib/manager/cargo/artifacts.ts index 1788c8d18c4e17..0627b9a15c4ea9 100644 --- a/lib/manager/cargo/artifacts.ts +++ b/lib/manager/cargo/artifacts.ts @@ -1,7 +1,8 @@ import { quote } from 'shlex'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { findLocalSiblingOrParent, readLocalFile, diff --git a/lib/manager/cargo/extract.spec.ts b/lib/manager/cargo/extract.spec.ts index 585ed908e334cf..9fa7553744585b 100644 --- a/lib/manager/cargo/extract.spec.ts +++ b/lib/manager/cargo/extract.spec.ts @@ -1,7 +1,7 @@ import { DirectoryResult, dir } from 'tmp-promise'; import { join } from 'upath'; import { loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { writeLocalFile } from '../../util/fs'; import type { ExtractConfig } from '../types'; @@ -29,11 +29,11 @@ describe('manager/cargo/extract', () => { cacheDir: join(tmpDir.path, 'cache'), }; - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterEach(async () => { await tmpDir.cleanup(); - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null for invalid toml', async () => { expect( @@ -131,7 +131,7 @@ describe('manager/cargo/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(1); - expect(res.deps[0].lookupName).toEqual('boolector'); + expect(res.deps[0].lookupName).toBe('boolector'); }); }); }); diff --git a/lib/manager/circleci/extract.ts b/lib/manager/circleci/extract.ts index 5b76e9cd3e2087..800c1eafdbebbe 100644 --- a/lib/manager/circleci/extract.ts +++ b/lib/manager/circleci/extract.ts @@ -24,7 +24,7 @@ export function extractPackageFile(content: string): PackageFile | null { logger.debug('orbNoop'); foundOrbOrNoop = true; lineNumber += 1; - continue; // eslint-disable-line no-continue + continue; } const orbMatch = regEx(/^\s+([^:]+):\s(.+)$/).exec(orbLine); // TODO #12071 if (orbMatch) { diff --git a/lib/manager/cocoapods/artifacts.spec.ts b/lib/manager/cocoapods/artifacts.spec.ts index 06bd91f6af1026..77ca05394ab466 100644 --- a/lib/manager/cocoapods/artifacts.spec.ts +++ b/lib/manager/cocoapods/artifacts.spec.ts @@ -3,11 +3,11 @@ import _fs from 'fs-extra'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { git, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as _datasource from '../../datasource'; import * as _env from '../../util/exec/env'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import type { UpdateArtifactsConfig } from '../types'; import { updateArtifacts } from '.'; @@ -37,7 +37,7 @@ describe('manager/cocoapods/artifacts', () => { jest.resetAllMocks(); env.getChildProcessEnv.mockReturnValue(envMock.basic); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); datasource.getPkgReleases.mockResolvedValue({ releases: [ @@ -51,7 +51,7 @@ describe('manager/cocoapods/artifacts', () => { }); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null if no Podfile.lock found', async () => { const execSnapshots = mockExecAll(exec); @@ -79,7 +79,7 @@ describe('manager/cocoapods/artifacts', () => { }); it('returns null for invalid local directory', async () => { const execSnapshots = mockExecAll(exec); - setGlobalConfig({ + GlobalConfig.set({ localDir: '', }); @@ -124,7 +124,7 @@ describe('manager/cocoapods/artifacts', () => { }); it('returns updated Podfile', async () => { const execSnapshots = mockExecAll(exec); - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('Old Podfile' as any); git.getRepoStatus.mockResolvedValueOnce({ modified: ['Podfile.lock'], @@ -142,7 +142,7 @@ describe('manager/cocoapods/artifacts', () => { }); it('returns updated Podfile and Pods files', async () => { const execSnapshots = mockExecAll(exec); - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('Old Manifest.lock' as any); fs.readFile.mockResolvedValueOnce('New Podfile' as any); fs.readFile.mockResolvedValueOnce('Pods manifest' as any); @@ -204,7 +204,7 @@ describe('manager/cocoapods/artifacts', () => { it('dynamically selects Docker image tag', async () => { const execSnapshots = mockExecAll(exec); - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('COCOAPODS: 1.2.4' as any); @@ -229,7 +229,7 @@ describe('manager/cocoapods/artifacts', () => { it('falls back to the `latest` Docker image tag', async () => { const execSnapshots = mockExecAll(exec); - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('COCOAPODS: 1.2.4' as any); datasource.getPkgReleases.mockResolvedValueOnce({ diff --git a/lib/manager/cocoapods/artifacts.ts b/lib/manager/cocoapods/artifacts.ts index 3fc124fa06cb7f..ba6adbad6dd483 100644 --- a/lib/manager/cocoapods/artifacts.ts +++ b/lib/manager/cocoapods/artifacts.ts @@ -2,7 +2,8 @@ import { quote } from 'shlex'; import { dirname, join } from 'upath'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { ensureCacheDir, getSiblingFileName, @@ -13,7 +14,7 @@ import { getRepoStatus } from '../../util/git'; import { regEx } from '../../util/regex'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; -const pluginRegex = /^\s*plugin\s*(['"])(?[^'"]+)\1/; // TODO #12070 +const pluginRegex = regEx(`^\\s*plugin\\s*(['"])(?[^'"]+)(['"])`); function getPluginCommands(content: string): string[] { const result = new Set(); diff --git a/lib/manager/cocoapods/extract.spec.ts b/lib/manager/cocoapods/extract.spec.ts index 3ff03c63673dac..b0e1a311d2ebf4 100644 --- a/lib/manager/cocoapods/extract.spec.ts +++ b/lib/manager/cocoapods/extract.spec.ts @@ -1,5 +1,5 @@ import { loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { extractPackageFile } from '.'; @@ -11,7 +11,7 @@ const adminConfig: RepoGlobalConfig = { localDir: '' }; describe('manager/cocoapods/extract', () => { describe('extractPackageFile()', () => { it('extracts from simple file', async () => { - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); const { deps } = await extractPackageFile(simplePodfile, 'Podfile'); expect(deps).toMatchSnapshot([ { depName: 'a' }, @@ -40,7 +40,7 @@ describe('manager/cocoapods/extract', () => { }); it('extracts from complex file', async () => { - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); const { deps } = await extractPackageFile(complexPodfile, 'Podfile'); expect(deps).toMatchSnapshot([ { depName: 'IQKeyboardManager', currentValue: '~> 6.5.0' }, diff --git a/lib/manager/cocoapods/extract.ts b/lib/manager/cocoapods/extract.ts index 09ce166170d749..84306227bda2a4 100644 --- a/lib/manager/cocoapods/extract.ts +++ b/lib/manager/cocoapods/extract.ts @@ -10,12 +10,14 @@ import type { PackageDependency, PackageFile } from '../types'; import type { ParsedLine } from './types'; const regexMappings = [ - /^\s*pod\s+(['"])(?[^'"/]+)(\/(?[^'"]+))?\1/, // TODO #12070 - /^\s*pod\s+(['"])[^'"]+\1\s*,\s*(['"])(?[^'"]+)\2\s*$/, // TODO #12070 - /,\s*:git\s*=>\s*(['"])(?[^'"]+)\1/, // TODO #12070 - /,\s*:tag\s*=>\s*(['"])(?[^'"]+)\1/, // TODO #12070 - /,\s*:path\s*=>\s*(['"])(?[^'"]+)\1/, // TODO #12070 - /^\s*source\s*(['"])(?[^'"]+)\1/, // TODO #12070 + regEx(`^\\s*pod\\s+(['"])(?[^'"/]+)(\\/(?[^'"]+))?(['"])`), + regEx( + `^\\s*pod\\s+(['"])[^'"]+(['"])\\s*,\\s*(['"])(?[^'"]+)(['"])\\s*$` + ), + regEx(`,\\s*:git\\s*=>\\s*(['"])(?[^'"]+)(['"])`), + regEx(`,\\s*:tag\\s*=>\\s*(['"])(?[^'"]+)(['"])`), + regEx(`,\\s*:path\\s*=>\\s*(['"])(?[^'"]+)(['"])`), + regEx(`^\\s*source\\s*(['"])(?[^'"]+)(['"])`), ]; export function parseLine(line: string): ParsedLine { diff --git a/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap b/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap index 51f7538dda1a63..7025b13c106048 100644 --- a/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap @@ -46,6 +46,30 @@ Array [ ] `; +exports[`manager/composer/artifacts disable plugins when configured locally 1`] = ` +Array [ + Object { + "cmd": "composer update foo bar --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + exports[`manager/composer/artifacts disables ignorePlatformReqs 1`] = ` Array [ Object { @@ -70,6 +94,116 @@ Array [ ] `; +exports[`manager/composer/artifacts does not disable plugins when configured globally 1`] = ` +Array [ + Object { + "cmd": "composer update foo bar --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`manager/composer/artifacts installs before running the update when symfony flex is installed 1`] = ` +Array [ + Object { + "cmd": "composer install --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, + Object { + "cmd": "composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`manager/composer/artifacts installs before running the update when symfony flex is installed as dev 1`] = ` +Array [ + Object { + "cmd": "composer install --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, + Object { + "cmd": "composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + exports[`manager/composer/artifacts performs lockFileMaintenance 1`] = ` Array [ Object { diff --git a/lib/manager/composer/__snapshots__/extract.spec.ts.snap b/lib/manager/composer/__snapshots__/extract.spec.ts.snap index 8e7f636a648c7c..1e83084218d8d8 100644 --- a/lib/manager/composer/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/composer/__snapshots__/extract.spec.ts.snap @@ -75,14 +75,12 @@ Object { "datasource": "packagist", "depName": "friendsofsymfony/user-bundle", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "fzaninotto/faker", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "1.0.1", @@ -95,7 +93,6 @@ Object { "datasource": "packagist", "depName": "jms/payment-core-bundle", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "1.1.0", @@ -174,28 +171,24 @@ Object { "datasource": "packagist", "depName": "behat/behat-bundle", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "behat/mink-bundle", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "behat/sahi-client", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "behat/common-contexts", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "^1.10.0", @@ -285,14 +278,12 @@ Object { "datasource": "packagist", "depName": "friendsofsymfony/user-bundle", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "fzaninotto/faker", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "1.0.1", @@ -305,7 +296,6 @@ Object { "datasource": "packagist", "depName": "jms/payment-core-bundle", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "1.1.0", @@ -384,28 +374,24 @@ Object { "datasource": "packagist", "depName": "behat/behat-bundle", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "behat/mink-bundle", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "behat/sahi-client", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "behat/common-contexts", "depType": "require-dev", - "skipReason": "any-version", }, Object { "currentValue": "^1.10.0", @@ -438,7 +424,6 @@ Object { "datasource": "packagist", "depName": "johnpbloch/wordpress", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "^2.0.1", @@ -457,56 +442,48 @@ Object { "datasource": "packagist", "depName": "wpackagist-plugin/tinymce-advanced", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "wpackagist-plugin/acf-content-analysis-for-yoast-seo", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "wpackagist-plugin/duplicate-post", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "wpackagist-plugin/simple-image-sizes", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "wpackagist-plugin/wordpress-seo", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "wpackagist-plugin/timber-library", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "wp-sync-db/wp-sync-db", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "*", "datasource": "packagist", "depName": "asha23/wp-seed-timber", "depType": "require", - "skipReason": "any-version", }, ], "managerData": Object { @@ -526,7 +503,6 @@ Object { "datasource": "packagist", "depName": "aws/aws-sdk-php", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "dev-trunk", @@ -563,7 +539,6 @@ Object { "datasource": "packagist", "depName": "aws/aws-sdk-php", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "^1.10.0", @@ -588,7 +563,6 @@ Object { "datasource": "packagist", "depName": "wpackagist-theme/hueman", "depType": "require", - "skipReason": "any-version", }, ], "registryUrls": Array [ @@ -605,7 +579,6 @@ Object { "datasource": "packagist", "depName": "aws/aws-sdk-php", "depType": "require", - "skipReason": "any-version", }, Object { "currentValue": "dev-trunk", diff --git a/lib/manager/composer/artifacts.spec.ts b/lib/manager/composer/artifacts.spec.ts index 42cfe6ea50ad2f..7ebeaa8f2a4281 100644 --- a/lib/manager/composer/artifacts.spec.ts +++ b/lib/manager/composer/artifacts.spec.ts @@ -1,13 +1,13 @@ import { join } from 'upath'; import { envMock, exec, mockExecAll } from '../../../test/exec-util'; import { env, fs, git, mocked, partial } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { PlatformId } from '../../constants'; import * as _datasource from '../../datasource'; import * as datasourcePackagist from '../../datasource/packagist'; import * as docker from '../../util/exec/docker'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import * as hostRules from '../../util/host-rules'; import type { UpdateArtifactsConfig } from '../types'; import * as composer from './artifacts'; @@ -26,6 +26,7 @@ const config: UpdateArtifactsConfig = { }; const adminConfig: RepoGlobalConfig = { + allowPlugins: false, allowScripts: false, // `join` fixes Windows CI localDir: join('/tmp/github/some/repo'), @@ -45,7 +46,7 @@ describe('manager/composer/artifacts', () => { env.getChildProcessEnv.mockReturnValue(envMock.basic); docker.resetPrefetchedImages(); hostRules.clear(); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); fs.ensureCacheDir.mockResolvedValue('/tmp/renovate/cache/others/composer'); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [ @@ -61,7 +62,7 @@ describe('manager/composer/artifacts', () => { }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns if no composer.lock found', async () => { @@ -79,8 +80,12 @@ describe('manager/composer/artifacts', () => { fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(exec); fs.readLocalFile.mockResolvedValueOnce('{}'); - git.getRepoStatus.mockResolvedValue(repoStatus); - setGlobalConfig({ ...adminConfig, allowScripts: true }); + git.getRepoStatus.mockResolvedValueOnce(repoStatus); + GlobalConfig.set({ + ...adminConfig, + allowScripts: true, + allowPlugins: true, + }); expect( await composer.updateArtifacts({ packageFileName: 'composer.json', @@ -132,7 +137,7 @@ describe('manager/composer/artifacts', () => { ...config, registryUrls: ['https://packagist.renovatebot.com'], }; - git.getRepoStatus.mockResolvedValue(repoStatus); + git.getRepoStatus.mockResolvedValueOnce(repoStatus); expect( await composer.updateArtifacts({ packageFileName: 'composer.json', @@ -148,7 +153,7 @@ describe('manager/composer/artifacts', () => { fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(exec); fs.readLocalFile.mockResolvedValueOnce('{}'); - git.getRepoStatus.mockResolvedValue({ + git.getRepoStatus.mockResolvedValueOnce({ ...repoStatus, modified: ['composer.lock'], }); @@ -200,7 +205,7 @@ describe('manager/composer/artifacts', () => { fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(exec); fs.readLocalFile.mockResolvedValueOnce('{ }'); - git.getRepoStatus.mockResolvedValue({ + git.getRepoStatus.mockResolvedValueOnce({ ...repoStatus, modified: ['composer.lock'], }); @@ -219,7 +224,7 @@ describe('manager/composer/artifacts', () => { }); it('supports docker mode', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(exec); @@ -236,7 +241,7 @@ describe('manager/composer/artifacts', () => { ], }); - git.getRepoStatus.mockResolvedValue({ + git.getRepoStatus.mockResolvedValueOnce({ ...repoStatus, modified: ['composer.lock'], }); @@ -254,11 +259,11 @@ describe('manager/composer/artifacts', () => { }); it('supports global mode', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'global' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(exec); fs.readLocalFile.mockResolvedValueOnce('{ }'); - git.getRepoStatus.mockResolvedValue({ + git.getRepoStatus.mockResolvedValueOnce({ ...repoStatus, modified: ['composer.lock'], }); @@ -328,7 +333,7 @@ describe('manager/composer/artifacts', () => { fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(exec); fs.readLocalFile.mockResolvedValueOnce('{ }'); - git.getRepoStatus.mockResolvedValue({ + git.getRepoStatus.mockResolvedValueOnce({ ...repoStatus, modified: ['composer.lock'], }); @@ -350,7 +355,7 @@ describe('manager/composer/artifacts', () => { fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(exec); fs.readLocalFile.mockResolvedValueOnce('{ }'); - git.getRepoStatus.mockResolvedValue({ + git.getRepoStatus.mockResolvedValueOnce({ ...repoStatus, modified: ['composer.lock'], }); @@ -367,4 +372,89 @@ describe('manager/composer/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + + it('installs before running the update when symfony flex is installed', async () => { + fs.readLocalFile.mockResolvedValueOnce( + '{"packages":[{"name":"symfony/flex","version":"1.17.1"}]}' + ); + const execSnapshots = mockExecAll(exec); + fs.readLocalFile.mockResolvedValueOnce('{ }'); + git.getRepoStatus.mockResolvedValueOnce({ + ...repoStatus, + modified: ['composer.lock'], + }); + expect( + await composer.updateArtifacts({ + packageFileName: 'composer.json', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + }, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toHaveLength(2); + }); + + it('installs before running the update when symfony flex is installed as dev', async () => { + fs.readLocalFile.mockResolvedValueOnce( + '{"packages-dev":[{"name":"symfony/flex","version":"1.17.1"}]}' + ); + const execSnapshots = mockExecAll(exec); + fs.readLocalFile.mockResolvedValueOnce('{ }'); + git.getRepoStatus.mockResolvedValueOnce({ + ...repoStatus, + modified: ['composer.lock'], + }); + expect( + await composer.updateArtifacts({ + packageFileName: 'composer.json', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + }, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toHaveLength(2); + }); + + it('does not disable plugins when configured globally', async () => { + fs.readLocalFile.mockResolvedValueOnce('{}'); + const execSnapshots = mockExecAll(exec); + fs.readLocalFile.mockResolvedValueOnce('{}'); + git.getRepoStatus.mockResolvedValueOnce(repoStatus); + GlobalConfig.set({ ...adminConfig, allowPlugins: true }); + expect( + await composer.updateArtifacts({ + packageFileName: 'composer.json', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: '{}', + config, + }) + ).toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('disable plugins when configured locally', async () => { + fs.readLocalFile.mockResolvedValueOnce('{}'); + const execSnapshots = mockExecAll(exec); + fs.readLocalFile.mockResolvedValueOnce('{}'); + git.getRepoStatus.mockResolvedValueOnce(repoStatus); + GlobalConfig.set({ ...adminConfig, allowPlugins: true }); + expect( + await composer.updateArtifacts({ + packageFileName: 'composer.json', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: '{}', + config: { + ...config, + ignorePlugins: true, + }, + }) + ).toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); }); diff --git a/lib/manager/composer/artifacts.ts b/lib/manager/composer/artifacts.ts index 929d64b588ef0b..78cfa37d0f294b 100644 --- a/lib/manager/composer/artifacts.ts +++ b/lib/manager/composer/artifacts.ts @@ -7,7 +7,8 @@ import { } from '../../constants/error-messages'; import * as datasourcePackagist from '../../datasource/packagist'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions, ToolConstraint } from '../../util/exec/types'; import { ensureCacheDir, ensureLocalDir, @@ -20,13 +21,13 @@ import { getRepoStatus } from '../../util/git'; import * as hostRules from '../../util/host-rules'; import { regEx } from '../../util/regex'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; -import type { AuthJson } from './types'; +import type { AuthJson, ComposerLock } from './types'; import { composerVersioningId, extractContraints, getComposerArguments, - getComposerConstraint, getPhpConstraint, + requireComposerDependencyInstallation, } from './utils'; function getAuthJson(): string | null { @@ -94,17 +95,16 @@ export async function updateArtifacts({ try { await writeLocalFile(packageFileName, newPackageFileContent); + const existingLockFile: ComposerLock = JSON.parse(existingLockFileContent); const constraints = { - ...extractContraints( - JSON.parse(newPackageFileContent), - JSON.parse(existingLockFileContent) - ), + ...extractContraints(JSON.parse(newPackageFileContent), existingLockFile), ...config.constraints, }; - const preCommands: string[] = [ - `install-tool composer ${await getComposerConstraint(constraints)}`, - ]; + const composerToolConstraint: ToolConstraint = { + toolName: 'composer', + constraint: constraints.composer, + }; const execOptions: ExecOptions = { cwdFile: packageFileName, @@ -112,13 +112,24 @@ export async function updateArtifacts({ COMPOSER_CACHE_DIR: await ensureCacheDir('composer'), COMPOSER_AUTH: getAuthJson(), }, + toolConstraints: [composerToolConstraint], docker: { - preCommands, image: 'php', tagConstraint: getPhpConstraint(constraints), tagScheme: composerVersioningId, }, }; + + const commands: string[] = []; + + // Determine whether install is required before update + if (requireComposerDependencyInstallation(existingLockFile)) { + const preCmd = 'composer'; + const preArgs = 'install' + getComposerArguments(config); + logger.debug({ preCmd, preArgs }, 'composer pre-update command'); + commands.push(`${preCmd} ${preArgs}`); + } + const cmd = 'composer'; let args: string; if (config.isLockFileMaintenance) { @@ -131,7 +142,9 @@ export async function updateArtifacts({ } args += getComposerArguments(config); logger.debug({ cmd, args }, 'composer command'); - await exec(`${cmd} ${args}`, execOptions); + commands.push(`${cmd} ${args}`); + + await exec(commands, execOptions); const status = await getRepoStatus(); if (!status.modified.includes(lockFileName)) { return null; diff --git a/lib/manager/composer/extract.ts b/lib/manager/composer/extract.ts index 754109701c918f..8eb3c6a4e78d63 100644 --- a/lib/manager/composer/extract.ts +++ b/lib/manager/composer/extract.ts @@ -41,11 +41,10 @@ function parseRepositories( Object.entries(repoJson).forEach(([key, repo]) => { if (is.object(repo)) { const name = is.array(repoJson) ? repo.name : key; - // eslint-disable-next-line default-case + switch (repo.type) { case 'vcs': case 'git': - // eslint-disable-next-line no-param-reassign repositories[name] = repo; break; case 'composer': @@ -131,7 +130,6 @@ export async function extractPackageFile( // Check custom repositories by type if (repositories[depName]) { - // eslint-disable-next-line default-case switch (repositories[depName].type) { case 'vcs': case 'git': @@ -152,9 +150,6 @@ export async function extractPackageFile( if (!depName.includes('/')) { dep.skipReason = SkipReason.Unsupported; } - if (currentValue === '*') { - dep.skipReason = SkipReason.AnyVersion; - } if (lockParsed) { const lockField = depType === 'require' diff --git a/lib/manager/composer/range.spec.ts b/lib/manager/composer/range.spec.ts index 91e53b001752d0..2f1d3621e0bbda 100644 --- a/lib/manager/composer/range.spec.ts +++ b/lib/manager/composer/range.spec.ts @@ -4,14 +4,14 @@ import { getRangeStrategy } from '.'; describe('manager/composer/range', () => { it('returns same if not auto', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('pins require-dev', () => { const config: RangeConfig = { rangeStrategy: 'auto', depType: 'require-dev', }; - expect(getRangeStrategy(config)).toEqual('pin'); + expect(getRangeStrategy(config)).toBe('pin'); }); it('pins project require', () => { const config: RangeConfig = { @@ -19,7 +19,7 @@ describe('manager/composer/range', () => { managerData: { composerJsonType: 'project' }, depType: 'require', }; - expect(getRangeStrategy(config)).toEqual('pin'); + expect(getRangeStrategy(config)).toBe('pin'); }); it('widens complex ranges', () => { const config: RangeConfig = { @@ -27,7 +27,7 @@ describe('manager/composer/range', () => { depType: 'require', currentValue: '^1.6.0 || ^2.0.0', }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('widens complex bump', () => { const config: RangeConfig = { @@ -35,10 +35,10 @@ describe('manager/composer/range', () => { depType: 'require', currentValue: '^1.6.0 || ^2.0.0', }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('defaults to replace', () => { const config: RangeConfig = { rangeStrategy: 'auto', depType: 'require' }; - expect(getRangeStrategy(config)).toEqual('replace'); + expect(getRangeStrategy(config)).toBe('replace'); }); }); diff --git a/lib/manager/composer/utils.spec.ts b/lib/manager/composer/utils.spec.ts index 516d751fe24c8f..5553063184f3f3 100644 --- a/lib/manager/composer/utils.spec.ts +++ b/lib/manager/composer/utils.spec.ts @@ -1,60 +1,13 @@ -import { mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; -import * as _datasource from '../../datasource'; +import { GlobalConfig } from '../../config/global'; import { extractContraints, getComposerArguments, - getComposerConstraint, + requireComposerDependencyInstallation, } from './utils'; jest.mock('../../../lib/datasource'); -const datasource = mocked(_datasource); - describe('manager/composer/utils', () => { - describe('getComposerConstraint', () => { - beforeEach(() => { - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [ - { version: '1.0.0' }, - { version: '1.1.0' }, - { version: '1.3.0' }, - { version: '2.0.14' }, - { version: '2.1.0' }, - ], - }); - }); - it('returns from config', async () => { - expect(await getComposerConstraint({ composer: '1.1.0' })).toEqual( - '1.1.0' - ); - }); - - it('returns from latest', async () => { - expect(await getComposerConstraint({})).toEqual('2.1.0'); - }); - - it('throws no releases', async () => { - datasource.getPkgReleases.mockReset(); - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [], - }); - await expect(getComposerConstraint({})).rejects.toThrow( - 'No composer releases found.' - ); - }); - - it('throws no compatible releases', async () => { - datasource.getPkgReleases.mockReset(); - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [{ version: '1.2.3' }], - }); - await expect( - getComposerConstraint({ composer: '^3.1.0' }) - ).rejects.toThrow('No compatible composer releases found.'); - }); - }); - describe('extractContraints', () => { it('returns from require', () => { expect( @@ -93,11 +46,11 @@ describe('manager/composer/utils', () => { describe('getComposerArguments', () => { afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('disables scripts and plugins by default', () => { - expect(getComposerArguments({})).toEqual( + expect(getComposerArguments({})).toBe( ' --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); @@ -106,7 +59,7 @@ describe('manager/composer/utils', () => { getComposerArguments({ composerIgnorePlatformReqs: [], }) - ).toEqual( + ).toBe( ' --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); @@ -115,7 +68,7 @@ describe('manager/composer/utils', () => { getComposerArguments({ composerIgnorePlatformReqs: ['ext-intl'], }) - ).toEqual( + ).toBe( ' --ignore-platform-req ext-intl --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); @@ -124,27 +77,75 @@ describe('manager/composer/utils', () => { getComposerArguments({ composerIgnorePlatformReqs: ['ext-intl', 'ext-icu'], }) - ).toEqual( + ).toBe( ' --ignore-platform-req ext-intl --ignore-platform-req ext-icu --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); - it('allows scripts/plugins when configured', () => { - setGlobalConfig({ + it('allows scripts when configured', () => { + GlobalConfig.set({ allowScripts: true, }); - expect(getComposerArguments({})).toEqual(' --no-ansi --no-interaction'); + expect(getComposerArguments({})).toBe( + ' --no-ansi --no-interaction --no-plugins' + ); }); - it('disables scripts/plugins when configured locally', () => { - setGlobalConfig({ + it('disables scripts when configured locally', () => { + GlobalConfig.set({ allowScripts: true, }); expect( getComposerArguments({ ignoreScripts: true, }) - ).toEqual( + ).toBe( + ' --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' + ); + }); + it('allows plugins when configured', () => { + GlobalConfig.set({ + allowPlugins: true, + }); + expect(getComposerArguments({})).toBe( + ' --no-ansi --no-interaction --no-scripts --no-autoloader' + ); + }); + it('disables plugins when configured locally', () => { + GlobalConfig.set({ + allowPlugins: true, + }); + expect( + getComposerArguments({ + ignorePlugins: true, + }) + ).toBe( ' --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); }); + + describe('requireComposerDependencyInstallation', () => { + it('returns true when symfony/flex has been installed', () => { + expect( + requireComposerDependencyInstallation({ + packages: [{ name: 'symfony/flex', version: '1.17.1' }], + }) + ).toBeTrue(); + }); + + it('returns true when symfony/flex has been installed as dev dependency', () => { + expect( + requireComposerDependencyInstallation({ + 'packages-dev': [{ name: 'symfony/flex', version: '1.17.1' }], + }) + ).toBeTrue(); + }); + + it('returns false when symfony/flex has not been installed', () => { + expect( + requireComposerDependencyInstallation({ + packages: [{ name: 'symfony/console', version: '5.4.0' }], + }) + ).toBeFalse(); + }); + }); }); diff --git a/lib/manager/composer/utils.ts b/lib/manager/composer/utils.ts index 57b837dc703c81..5b6b3a2c00292d 100644 --- a/lib/manager/composer/utils.ts +++ b/lib/manager/composer/utils.ts @@ -1,6 +1,5 @@ import { quote } from 'shlex'; -import { getGlobalConfig } from '../../config/global'; -import { getPkgReleases } from '../../datasource'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import { api, id as composerVersioningId } from '../../versioning/composer'; import type { UpdateArtifactsConfig } from '../types'; @@ -8,6 +7,8 @@ import type { ComposerConfig, ComposerLock } from './types'; export { composerVersioningId }; +const depRequireInstall = new Set(['symfony/flex']); + export function getComposerArguments(config: UpdateArtifactsConfig): string { let args = ''; @@ -22,50 +23,15 @@ export function getComposerArguments(config: UpdateArtifactsConfig): string { } args += ' --no-ansi --no-interaction'; - if (!getGlobalConfig().allowScripts || config.ignoreScripts) { - args += ' --no-scripts --no-autoloader --no-plugins'; - } - - return args; -} - -export async function getComposerConstraint( - constraints: Record -): Promise { - const { composer } = constraints; - - if (api.isSingleVersion(composer)) { - logger.debug( - { version: composer }, - 'Using composer constraint from config' - ); - return composer; + if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) { + args += ' --no-scripts --no-autoloader'; } - const release = await getPkgReleases({ - depName: 'composer/composer', - datasource: 'github-releases', - versioning: composerVersioningId, - }); - - if (!release?.releases?.length) { - throw new Error('No composer releases found.'); + if (!GlobalConfig.get('allowPlugins') || config.ignorePlugins) { + args += ' --no-plugins'; } - let versions = release.releases.map((r) => r.version); - if (composer) { - versions = versions.filter( - (v) => api.isValid(v) && api.matches(v, composer) - ); - } - - if (!versions.length) { - throw new Error('No compatible composer releases found.'); - } - - const version = versions.pop(); - logger.debug({ range: composer, version }, 'Using composer constraint'); - return version; + return args; } export function getPhpConstraint(constraints: Record): string { @@ -79,6 +45,15 @@ export function getPhpConstraint(constraints: Record): string { return null; } +export function requireComposerDependencyInstallation( + lock: ComposerLock +): boolean { + return ( + lock.packages?.some((p) => depRequireInstall.has(p.name)) === true || + lock['packages-dev']?.some((p) => depRequireInstall.has(p.name)) === true + ); +} + export function extractContraints( composerJson: ComposerConfig, lockParsed: ComposerLock diff --git a/lib/manager/dockerfile/extract.spec.ts b/lib/manager/dockerfile/extract.spec.ts index 0bd7c01e625b38..6861578bd3842e 100644 --- a/lib/manager/dockerfile/extract.spec.ts +++ b/lib/manager/dockerfile/extract.spec.ts @@ -191,8 +191,8 @@ describe('manager/dockerfile/extract', () => { }, ] `); - expect(res[0].depName).toEqual('registry2.something.info:5005/node'); - expect(res[0].currentValue).toEqual('8'); + expect(res[0].depName).toBe('registry2.something.info:5005/node'); + expect(res[0].currentValue).toBe('8'); }); it('handles custom hosts with port without tag', () => { @@ -212,7 +212,7 @@ describe('manager/dockerfile/extract', () => { }, ] `); - expect(res[0].depName).toEqual('registry2.something.info:5005/node'); + expect(res[0].depName).toBe('registry2.something.info:5005/node'); }); it('handles quay hosts with port', () => { @@ -619,7 +619,6 @@ describe('manager/dockerfile/extract', () => { }); it('handles default environment variable values', () => { - // eslint-disable-next-line no-template-curly-in-string const res = getDep('${REDIS_IMAGE:-redis:5.0.0@sha256:abcd}'); expect(res).toMatchInlineSnapshot(` Object { @@ -632,7 +631,6 @@ Object { } `); - // eslint-disable-next-line no-template-curly-in-string const res2 = getDep('${REDIS_IMAGE:-redis:5.0.0}'); expect(res2).toMatchInlineSnapshot(` Object { @@ -644,7 +642,6 @@ Object { } `); - // eslint-disable-next-line no-template-curly-in-string const res3 = getDep('${REDIS_IMAGE:-redis@sha256:abcd}'); expect(res3).toMatchInlineSnapshot(` Object { @@ -658,7 +655,6 @@ Object { }); it('skips tag containing a variable', () => { - // eslint-disable-next-line no-template-curly-in-string const res = getDep('mcr.microsoft.com/dotnet/sdk:5.0${IMAGESUFFIX}'); expect(res).toMatchInlineSnapshot(` Object { diff --git a/lib/manager/dockerfile/extract.ts b/lib/manager/dockerfile/extract.ts index 0053192215b955..64027d89270656 100644 --- a/lib/manager/dockerfile/extract.ts +++ b/lib/manager/dockerfile/extract.ts @@ -153,7 +153,7 @@ export function extractPackageFile(content: string): PackageFile | null { const stageNames: string[] = []; const fromMatches = content.matchAll( - /^[ \t]*FROM(?:\\\r?\n| |\t|#.*?\r?\n|[ \t]--[a-z]+=\S+?)*[ \t](?\S+)(?:(?:\\\r?\n| |\t|#.*\r?\n)+as[ \t]+(?\S+))?/gim // TODO #12070 + /^[ \t]*FROM(?:\\\r?\n| |\t|#.*?\r?\n|[ \t]--[a-z]+=\S+?)*[ \t](?\S+)(?:(?:\\\r?\n| |\t|#.*\r?\n)+as[ \t]+(?\S+))?/gim // TODO #12875 complex for re2 has too many not supported groups ); for (const fromMatch of fromMatches) { @@ -180,7 +180,7 @@ export function extractPackageFile(content: string): PackageFile | null { } const copyFromMatches = content.matchAll( - /^[ \t]*COPY(?:\\\r?\n| |\t|#.*\r?\n|[ \t]--[a-z]+=\w+?)*[ \t]--from=(?\S+)/gim // TODO #12070 + /^[ \t]*COPY(?:\\\r?\n| |\t|#.*\r?\n|[ \t]--[a-z]+=\w+?)*[ \t]--from=(?\S+)/gim // TODO #12875 complex for re2 has too many not supported groups ); for (const copyFromMatch of copyFromMatches) { diff --git a/lib/manager/git-submodules/extract.spec.ts b/lib/manager/git-submodules/extract.spec.ts index 12de81b03ce9e7..9fe186ed5ee2f1 100644 --- a/lib/manager/git-submodules/extract.spec.ts +++ b/lib/manager/git-submodules/extract.spec.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; import _simpleGit, { Response, SimpleGit } from 'simple-git'; import { partial } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import * as hostRules from '../../util/host-rules'; import type { PackageFile } from '../types'; import extractPackageFile from './extract'; @@ -44,20 +44,20 @@ describe('manager/git-submodules/extract', () => { }); describe('extractPackageFile()', () => { it('extracts submodules', async () => { - setGlobalConfig({ localDir: `${__dirname}/__fixtures__` }); + GlobalConfig.set({ localDir: `${__dirname}/__fixtures__` }); hostRules.add({ matchHost: 'github.com', token: '123test' }); let res: PackageFile; expect(await extractPackageFile('', '.gitmodules.1', {})).toBeNull(); res = await extractPackageFile('', '.gitmodules.2', {}); expect(res.deps).toHaveLength(1); - expect(res.deps[0].currentValue).toEqual('main'); + expect(res.deps[0].currentValue).toBe('main'); res = await extractPackageFile('', '.gitmodules.3', {}); expect(res.deps).toHaveLength(1); res = await extractPackageFile('', '.gitmodules.4', {}); expect(res.deps).toHaveLength(1); res = await extractPackageFile('', '.gitmodules.5', {}); expect(res.deps).toHaveLength(3); - expect(res.deps[2].lookupName).toEqual( + expect(res.deps[2].lookupName).toBe( 'https://github.com/renovatebot/renovate-config.git' ); }); diff --git a/lib/manager/git-submodules/extract.ts b/lib/manager/git-submodules/extract.ts index 0017b70e8d6fc3..f67c0b46a7def9 100644 --- a/lib/manager/git-submodules/extract.ts +++ b/lib/manager/git-submodules/extract.ts @@ -1,7 +1,7 @@ import URL from 'url'; import Git, { SimpleGit } from 'simple-git'; import upath from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { GitRefsDatasource } from '../../datasource/git-refs'; import { logger } from '../../logger'; import { simpleGitConfig } from '../../util/git/config'; @@ -90,7 +90,7 @@ export default async function extractPackageFile( fileName: string, config: ExtractConfig ): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const git = Git(localDir); const gitModulesPath = upath.join(localDir, fileName); diff --git a/lib/manager/git-submodules/update.spec.ts b/lib/manager/git-submodules/update.spec.ts index 3e9b6c7f73e787..0e95061ea081d0 100644 --- a/lib/manager/git-submodules/update.spec.ts +++ b/lib/manager/git-submodules/update.spec.ts @@ -1,7 +1,7 @@ import _simpleGit from 'simple-git'; import { DirectoryResult, dir } from 'tmp-promise'; import { join } from 'upath'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import type { Upgrade } from '../types'; import updateDependency from './update'; @@ -19,11 +19,11 @@ describe('manager/git-submodules/update', () => { tmpDir = await dir({ unsafeCleanup: true }); adminConfig = { localDir: join(tmpDir.path) }; - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterAll(async () => { await tmpDir.cleanup(); - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null on error', async () => { simpleGit.mockReturnValue({ @@ -50,7 +50,7 @@ describe('manager/git-submodules/update', () => { fileContent: '', upgrade, }); - expect(update).toEqual(''); + expect(update).toBe(''); }); }); }); diff --git a/lib/manager/git-submodules/update.ts b/lib/manager/git-submodules/update.ts index 314fa05aebbc93..ee4c432e577bf2 100644 --- a/lib/manager/git-submodules/update.ts +++ b/lib/manager/git-submodules/update.ts @@ -1,6 +1,6 @@ import Git from 'simple-git'; import upath from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import type { UpdateDependencyConfig } from '../types'; @@ -8,7 +8,7 @@ export default async function updateDependency({ fileContent, upgrade, }: UpdateDependencyConfig): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const git = Git(localDir); const submoduleGit = Git(upath.join(localDir, upgrade.depName)); diff --git a/lib/manager/github-actions/extract.ts b/lib/manager/github-actions/extract.ts index d5a7bbd234c638..42e8c4789ee216 100644 --- a/lib/manager/github-actions/extract.ts +++ b/lib/manager/github-actions/extract.ts @@ -19,7 +19,7 @@ export function extractPackageFile(content: string): PackageFile | null { const deps: PackageDependency[] = []; for (const line of content.split('\n')) { if (line.trim().startsWith('#')) { - continue; // eslint-disable-line no-continue + continue; } const dockerMatch = dockerRe.exec(line); @@ -29,7 +29,7 @@ export function extractPackageFile(content: string): PackageFile | null { dep.depType = 'docker'; dep.versioning = dockerVersioning.id; deps.push(dep); - continue; // eslint-disable-line no-continue + continue; } const tagMatch = actionRe.exec(line); diff --git a/lib/manager/gitlabci-include/extract.spec.ts b/lib/manager/gitlabci-include/extract.spec.ts index c7729dd9e86e00..b5a36706e704db 100644 --- a/lib/manager/gitlabci-include/extract.spec.ts +++ b/lib/manager/gitlabci-include/extract.spec.ts @@ -44,7 +44,7 @@ describe('manager/gitlabci-include/extract', () => { const res = extractPackageFile(yamlFileMultiConfig, '.gitlab-ci.yml', { endpoint, }); - expect(res.deps[0].registryUrls[0]).toEqual('http://gitlab.test'); + expect(res.deps[0].registryUrls[0]).toBe('http://gitlab.test'); } }); }); diff --git a/lib/manager/gitlabci/extract.spec.ts b/lib/manager/gitlabci/extract.spec.ts index 04c6ec92254344..ca18521d5c0a71 100644 --- a/lib/manager/gitlabci/extract.spec.ts +++ b/lib/manager/gitlabci/extract.spec.ts @@ -1,5 +1,5 @@ import { logger } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import type { ExtractConfig, PackageDependency } from '../types'; import { extractAllPackageFiles } from './extract'; @@ -10,11 +10,11 @@ const adminConfig: RepoGlobalConfig = { localDir: '' }; describe('manager/gitlabci/extract', () => { beforeEach(() => { - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); describe('extractAllPackageFiles()', () => { diff --git a/lib/manager/gitlabci/extract.ts b/lib/manager/gitlabci/extract.ts index 2d0fee6ce64442..14a478b453af2b 100644 --- a/lib/manager/gitlabci/extract.ts +++ b/lib/manager/gitlabci/extract.ts @@ -2,17 +2,21 @@ import is from '@sindresorhus/is'; import { load } from 'js-yaml'; import { logger } from '../../logger'; import { readLocalFile } from '../../util/fs'; +import { regEx } from '../../util/regex'; import { getDep } from '../dockerfile/extract'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types'; import type { GitlabPipeline } from './types'; import { replaceReferenceTags } from './utils'; -const commentsRe = /^\s*#/; // TODO #12070 -const whitespaceRe = /^(?\s*)/; // TODO #12070 -const imageRe = - /^(?\s*)image:(?:\s+['"]?(?[^\s'"]+)['"]?)?\s*$/; // TODO #12070 -const nameRe = /^\s*name:\s+['"]?(?[^\s'"]+)['"]?\s*$/; // TODO #12070 -const serviceRe = /^\s*-\s*(?:name:\s+)?['"]?(?[^\s'"]+)['"]?\s*$/; // TODO #12070 +const commentsRe = regEx(/^\s*#/); +const whitespaceRe = regEx(`^(?\\s*)`); +const imageRe = regEx( + `^(?\\s*)image:(?:\\s+['"]?(?[^\\s'"]+)['"]?)?\\s*$` +); +const nameRe = regEx(`^\\s*name:\\s+['"]?(?[^\\s'"]+)['"]?\\s*$`); +const serviceRe = regEx( + `^\\s*-\\s*(?:name:\\s+)?['"]?(?[^\\s'"]+)['"]?\\s*$` +); function skipCommentLines( lines: string[], lineNumber: number @@ -61,7 +65,7 @@ export function extractPackageFile(content: string): PackageFile | null { } } } - const services = /^\s*services:\s*$/.test(line); // TODO #12071 #12070 + const services = regEx(/^\s*services:\s*$/).test(line); // TODO #12071 if (services) { logger.trace(`Matched services on line ${lineNumber}`); let foundImage: boolean; @@ -105,7 +109,7 @@ export async function extractAllPackageFiles( const content = await readLocalFile(file, 'utf8'); if (!content) { logger.debug({ file }, 'Empty or non existent gitlabci file'); - // eslint-disable-next-line no-continue + continue; } let doc: GitlabPipeline; @@ -120,7 +124,7 @@ export async function extractAllPackageFiles( if (is.array(doc?.include)) { for (const includeObj of doc.include) { if (is.string(includeObj.local)) { - const fileObj = includeObj.local.replace(/^\//, ''); // TODO #12071 #12070 + const fileObj = includeObj.local.replace(regEx(/^\//), ''); if (!seen.has(fileObj)) { seen.add(fileObj); filesToExamine.push(fileObj); @@ -128,7 +132,7 @@ export async function extractAllPackageFiles( } } } else if (is.string(doc?.include)) { - const fileObj = doc.include.replace(/^\//, ''); // TODO #12071 #12070 + const fileObj = doc.include.replace(regEx(/^\//), ''); if (!seen.has(fileObj)) { seen.add(fileObj); filesToExamine.push(fileObj); diff --git a/lib/manager/gomod/artifacts.spec.ts b/lib/manager/gomod/artifacts.spec.ts index 482842efc805ec..c4a2d881f56427 100644 --- a/lib/manager/gomod/artifacts.spec.ts +++ b/lib/manager/gomod/artifacts.spec.ts @@ -3,11 +3,11 @@ import _fs from 'fs-extra'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { git, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as docker from '../../util/exec/docker'; import * as _env from '../../util/exec/env'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import * as _hostRules from '../../util/host-rules'; import type { UpdateArtifactsConfig } from '../types'; import * as gomod from './artifacts'; @@ -61,11 +61,11 @@ describe('manager/gomod/artifacts', () => { delete process.env.GOPATH; env.getChildProcessEnv.mockReturnValue({ ...envMock.basic, ...goEnv }); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns if no go.sum found', async () => { const execSnapshots = mockExecAll(exec); @@ -151,7 +151,7 @@ describe('manager/gomod/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('supports docker mode without credentials', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename const execSnapshots = mockExecAll(exec); @@ -170,7 +170,7 @@ describe('manager/gomod/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('supports global mode', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'global' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename const execSnapshots = mockExecAll(exec); @@ -189,7 +189,7 @@ describe('manager/gomod/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('supports docker mode with credentials', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.find.mockReturnValueOnce({ token: 'some-token', }); @@ -212,7 +212,7 @@ describe('manager/gomod/artifacts', () => { }); it('supports docker mode with 2 credentials', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.find.mockReturnValueOnce({ token: 'some-token', }); @@ -256,7 +256,7 @@ describe('manager/gomod/artifacts', () => { }); it('supports docker mode with single credential', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.getAll.mockReturnValueOnce([ { token: 'some-enterprise-token', @@ -295,7 +295,7 @@ describe('manager/gomod/artifacts', () => { }); it('supports docker mode with multiple credentials for different paths', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.getAll.mockReturnValueOnce([ { token: 'some-enterprise-token-repo1', @@ -341,7 +341,7 @@ describe('manager/gomod/artifacts', () => { }); it('supports docker mode and ignores non http credentials', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.getAll.mockReturnValueOnce([ { token: 'some-token', @@ -384,7 +384,7 @@ describe('manager/gomod/artifacts', () => { }); it('supports docker mode with many credentials', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.find.mockReturnValueOnce({ token: 'some-token', }); @@ -442,7 +442,7 @@ describe('manager/gomod/artifacts', () => { }); it('supports docker mode and ignores non git credentials', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.find.mockReturnValueOnce({ token: 'some-token', }); @@ -484,7 +484,7 @@ describe('manager/gomod/artifacts', () => { }); it('supports docker mode with goModTidy', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); hostRules.find.mockReturnValueOnce({}); fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts index 14c81c734198f6..dbb2083ef90a72 100644 --- a/lib/manager/gomod/artifacts.ts +++ b/lib/manager/gomod/artifacts.ts @@ -1,10 +1,11 @@ import is from '@sindresorhus/is'; import { dirname, join } from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { PlatformId } from '../../constants'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { ensureCacheDir, readLocalFile, writeLocalFile } from '../../util/fs'; import { getRepoStatus } from '../../util/git'; import { getGitAuthenticatedEnvironmentVariables } from '../../util/git/auth'; @@ -171,7 +172,7 @@ export async function updateArtifacts({ GONOSUMDB: process.env.GONOSUMDB, GOSUMDB: process.env.GOSUMDB, GOFLAGS: useModcacherw(config.constraints?.go) ? '-modcacherw' : null, - CGO_ENABLED: getGlobalConfig().binarySource === 'docker' ? '0' : null, + CGO_ENABLED: GlobalConfig.get('binarySource') === 'docker' ? '0' : null, ...getGitEnvironmentVariables(), }, docker: { diff --git a/lib/manager/gomod/extract.spec.ts b/lib/manager/gomod/extract.spec.ts index 45889a2c516767..b702220681b0d7 100644 --- a/lib/manager/gomod/extract.spec.ts +++ b/lib/manager/gomod/extract.spec.ts @@ -20,7 +20,7 @@ describe('manager/gomod/extract', () => { it('extracts constraints', () => { const res = extractPackageFile(gomod3); expect(res).toMatchSnapshot(); - expect(res.constraints.go).toEqual('^1.13'); + expect(res.constraints.go).toBe('^1.13'); }); it('extracts multi-line requires', () => { const res = extractPackageFile(gomod2).deps; diff --git a/lib/manager/gradle-wrapper/artifacts-real.spec.ts b/lib/manager/gradle-wrapper/artifacts-real.spec.ts index 88fe508870ebd4..e5ff7af1289a37 100644 --- a/lib/manager/gradle-wrapper/artifacts-real.spec.ts +++ b/lib/manager/gradle-wrapper/artifacts-real.spec.ts @@ -3,9 +3,9 @@ import Git from 'simple-git'; import { resolve } from 'upath'; import * as httpMock from '../../../test/http-mock'; import { git, partial } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import { ifSystemSupportsGradle } from '../gradle/deep/__testutil__/gradle'; import type { UpdateArtifactsConfig } from '../types'; import * as gradleWrapper from '.'; @@ -42,12 +42,12 @@ describe('manager/gradle-wrapper/artifacts-real', () => { beforeEach(() => { jest.resetAllMocks(); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterEach(async () => { await Git(fixtures).checkout(['HEAD', '--', '.']); - setGlobalConfig(); + GlobalConfig.reset(); }); it('replaces existing value', async () => { @@ -151,10 +151,10 @@ describe('manager/gradle-wrapper/artifacts-real', () => { config, }); - expect(res[0].artifactError.lockFile).toEqual( + expect(res[0].artifactError.lockFile).toBe( 'gradle/wrapper/gradle-wrapper.properties' ); - expect(res[0].artifactError.stderr).toEqual('failed'); + expect(res[0].artifactError.stderr).toBe('failed'); // 5.6.4 => 5.6.4 (updates execs) - unexpected behavior (looks like a bug in Gradle) ['gradle/wrapper/gradle-wrapper.properties'].forEach((file) => { @@ -168,7 +168,7 @@ describe('manager/gradle-wrapper/artifacts-real', () => { localDir: resolve(fixtures, './wrongCmd'), }; - setGlobalConfig(wrongCmdConfig); + GlobalConfig.set(wrongCmdConfig); const res = await gradleWrapper.updateArtifacts({ packageFileName: 'gradle/wrapper/gradle-wrapper.properties', updatedDeps: [], @@ -178,11 +178,11 @@ describe('manager/gradle-wrapper/artifacts-real', () => { config, }); - expect(res[0].artifactError.lockFile).toEqual( + expect(res[0].artifactError.lockFile).toBe( 'gradle/wrapper/gradle-wrapper.properties' ); expect(res[0].artifactError.stderr).not.toBeNull(); - expect(res[0].artifactError.stderr).not.toEqual(''); + expect(res[0].artifactError.stderr).not.toBe(''); // 5.6.4 => 5.6.4 (updates execs) - unexpected behavior (looks like a bug in Gradle) ['gradle/wrapper/gradle-wrapper.properties'].forEach((file) => { @@ -191,7 +191,7 @@ describe('manager/gradle-wrapper/artifacts-real', () => { }); it('gradlew not found', async () => { - setGlobalConfig({ localDir: 'some-dir' }); + GlobalConfig.set({ localDir: 'some-dir' }); const res = await gradleWrapper.updateArtifacts({ packageFileName: 'gradle-wrapper.properties', updatedDeps: [], diff --git a/lib/manager/gradle-wrapper/artifacts.spec.ts b/lib/manager/gradle-wrapper/artifacts.spec.ts index a98cedc9d94fd3..4d042f021fdfbd 100644 --- a/lib/manager/gradle-wrapper/artifacts.spec.ts +++ b/lib/manager/gradle-wrapper/artifacts.spec.ts @@ -9,10 +9,10 @@ import { git, partial, } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { resetPrefetchedImages } from '../../util/exec/docker'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import type { UpdateArtifactsConfig } from '../types'; import * as gradleWrapper from '.'; @@ -50,7 +50,7 @@ describe('manager/gradle-wrapper/artifacts', () => { LC_ALL: 'en_US', }); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); resetPrefetchedImages(); fs.readLocalFile.mockResolvedValue('test'); @@ -58,7 +58,7 @@ describe('manager/gradle-wrapper/artifacts', () => { }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('replaces existing value', async () => { @@ -97,7 +97,7 @@ describe('manager/gradle-wrapper/artifacts', () => { }); it('gradlew not found', async () => { - setGlobalConfig({ ...adminConfig, localDir: 'some-dir' }); + GlobalConfig.set({ ...adminConfig, localDir: 'some-dir' }); const res = await gradleWrapper.updateArtifacts({ packageFileName: 'gradle-wrapper.properties', updatedDeps: [], diff --git a/lib/manager/gradle-wrapper/artifacts.ts b/lib/manager/gradle-wrapper/artifacts.ts index 3dda4a837c9f89..2facba0cf0d694 100644 --- a/lib/manager/gradle-wrapper/artifacts.ts +++ b/lib/manager/gradle-wrapper/artifacts.ts @@ -1,11 +1,13 @@ import { quote } from 'shlex'; import { resolve } from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { readLocalFile, stat, writeLocalFile } from '../../util/fs'; -import { StatusResult, getRepoStatus } from '../../util/git'; +import { getRepoStatus } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import { Http } from '../../util/http'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; import { @@ -57,7 +59,7 @@ export async function updateArtifacts({ config, }: UpdateArtifact): Promise { try { - const { localDir: projectDir } = getGlobalConfig(); + const projectDir = GlobalConfig.get('localDir'); logger.debug({ updatedDeps }, 'gradle-wrapper.updateArtifacts()'); const gradlew = gradleWrapperFileName(); const gradlewPath = resolve(projectDir, `./${gradlew}`); @@ -126,7 +128,7 @@ export async function updateArtifacts({ addIfUpdated(status, fileProjectPath) ) ) - ).filter((e) => e != null); + ).filter(Boolean); logger.debug( { files: updateArtifactsResult.map((r) => r.file.name) }, `Returning updated gradle-wrapper files` diff --git a/lib/manager/gradle-wrapper/util.spec.ts b/lib/manager/gradle-wrapper/util.spec.ts index 9a65c9c4b9c5d2..e48174e5e44bdd 100644 --- a/lib/manager/gradle-wrapper/util.spec.ts +++ b/lib/manager/gradle-wrapper/util.spec.ts @@ -1,4 +1,4 @@ -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { extractGradleVersion, getJavaContraint } from './utils'; describe('manager/gradle-wrapper/util', () => { @@ -8,23 +8,23 @@ describe('manager/gradle-wrapper/util', () => { }); it('return ^11.0.0 for docker mode and undefined gradle', () => { - setGlobalConfig({ binarySource: 'docker' }); - expect(getJavaContraint(undefined)).toEqual('^11.0.0'); + GlobalConfig.set({ binarySource: 'docker' }); + expect(getJavaContraint(undefined)).toBe('^11.0.0'); }); it('return ^8.0.0 for docker gradle < 5', () => { - setGlobalConfig({ binarySource: 'docker' }); - expect(getJavaContraint('4.9')).toEqual('^8.0.0'); + GlobalConfig.set({ binarySource: 'docker' }); + expect(getJavaContraint('4.9')).toBe('^8.0.0'); }); it('return ^11.0.0 for docker gradle >=5 && <7', () => { - setGlobalConfig({ binarySource: 'docker' }); - expect(getJavaContraint('6.0')).toEqual('^11.0.0'); + GlobalConfig.set({ binarySource: 'docker' }); + expect(getJavaContraint('6.0')).toBe('^11.0.0'); }); it('return ^16.0.0 for docker gradle >= 7', () => { - setGlobalConfig({ binarySource: 'docker' }); - expect(getJavaContraint('7.0.1')).toEqual('^16.0.0'); + GlobalConfig.set({ binarySource: 'docker' }); + expect(getJavaContraint('7.0.1')).toBe('^16.0.0'); }); }); diff --git a/lib/manager/gradle-wrapper/utils.ts b/lib/manager/gradle-wrapper/utils.ts index eb3f75966de5d3..99f389ed196f0a 100644 --- a/lib/manager/gradle-wrapper/utils.ts +++ b/lib/manager/gradle-wrapper/utils.ts @@ -1,7 +1,7 @@ import type { Stats } from 'fs'; import os from 'os'; import upath from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import { chmod } from '../../util/fs'; import { regEx } from '../../util/regex'; @@ -17,7 +17,7 @@ export const extraEnv = { export function gradleWrapperFileName(): string { if ( os.platform() === 'win32' && - getGlobalConfig()?.binarySource !== 'docker' + GlobalConfig.get('binarySource') !== 'docker' ) { return 'gradlew.bat'; } @@ -30,7 +30,6 @@ export async function prepareGradleCommand( gradlew: Stats | null, args: string | null ): Promise { - /* eslint-disable no-bitwise */ // istanbul ignore if if (gradlew?.isFile() === true) { // if the file is not executable by others @@ -54,7 +53,7 @@ export async function prepareGradleCommand( * @returns A Java semver range */ export function getJavaContraint(gradleVersion: string): string | null { - if (getGlobalConfig()?.binarySource !== 'docker') { + if (GlobalConfig.get('binarySource') !== 'docker') { // ignore return null; } diff --git a/lib/manager/gradle/deep/__testutil__/gradle.ts b/lib/manager/gradle/deep/__testutil__/gradle.ts index 444accb9a872d1..41568651fb2635 100644 --- a/lib/manager/gradle/deep/__testutil__/gradle.ts +++ b/lib/manager/gradle/deep/__testutil__/gradle.ts @@ -28,7 +28,7 @@ ${javaVersionOutput}`); let cachedJavaVersion: number | null = null; function determineJavaVersion(): number { - if (cachedJavaVersion == null) { + if (!cachedJavaVersion) { let javaVersionCommand: SpawnSyncReturns; let error: Error; try { diff --git a/lib/manager/gradle/deep/build-gradle.spec.ts b/lib/manager/gradle/deep/build-gradle.spec.ts index f12807a96f1274..a0db8c000347a8 100644 --- a/lib/manager/gradle/deep/build-gradle.spec.ts +++ b/lib/manager/gradle/deep/build-gradle.spec.ts @@ -32,7 +32,7 @@ describe('manager/gradle/deep/build-gradle', () => { { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' }, '7.0.0' ); - expect(updatedGradleFile).toEqual( + expect(updatedGradleFile).toBe( "runtime ( 'mysql:mysql-connector-java:7.0.0' )" ); }); @@ -49,7 +49,7 @@ describe('manager/gradle/deep/build-gradle', () => { }, '2.10.1' ); - expect(updatedGradleFile).toEqual( + expect(updatedGradleFile).toBe( "runtime ( 'com.crashlytics.sdk.android:crashlytics:2.10.1@aar' )" ); }); @@ -61,7 +61,7 @@ describe('manager/gradle/deep/build-gradle', () => { { group: 'junit', name: 'junit', version: '4.0' }, '5.0' ); - expect(updatedGradleFile).toEqual( + expect(updatedGradleFile).toBe( "runtime ( 'junit:junit:5.0:javadoc@jar' )" ); }); @@ -560,7 +560,7 @@ describe('manager/gradle/deep/build-gradle', () => { it('should replace a external groovy variable assigned to a specific dependency', () => { const gradleFile = - 'runtime ( "mysql:mysql-connector-java:${mysqlVersion}" )'; // eslint-disable-line no-template-curly-in-string + 'runtime ( "mysql:mysql-connector-java:${mysqlVersion}" )'; const mysqlDependency = { group: 'mysql', depGroup: 'mysql', @@ -575,12 +575,12 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('String mysqlVersion = "7.0.0"'); + expect(updatedGradleFile).toBe('String mysqlVersion = "7.0.0"'); }); it('should replace a external groovy map variable assigned to a specific dependency', () => { const gradleFile = - 'runtime ( "mysql:mysql-connector-java:${versions.mysqlVersion}" )'; // eslint-disable-line no-template-curly-in-string + 'runtime ( "mysql:mysql-connector-java:${versions.mysqlVersion}" )'; const mysqlDependency = { group: 'mysql', depGroup: 'mysql', @@ -595,14 +595,12 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual( - 'ext.versions = [ mysqlVersion: "7.0.0" ]' - ); + expect(updatedGradleFile).toBe('ext.versions = [ mysqlVersion: "7.0.0" ]'); }); it('should replace a external groovy map nested variable assigned to a specific dependency', () => { const gradleFile = - 'runtime ( "mysql:mysql-connector-java:${versions.nested.mysqlVersion}" )'; // eslint-disable-line no-template-curly-in-string + 'runtime ( "mysql:mysql-connector-java:${versions.nested.mysqlVersion}" )'; const mysqlDependency = { group: 'mysql', depGroup: 'mysql', @@ -618,14 +616,14 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual( + expect(updatedGradleFile).toBe( 'ext.versions = [ nested: [ mysqlVersion: "7.0.0" ] ]' ); }); it('should replace a external property variable assigned to a specific dependency', () => { const gradleFile = - 'runtime ( "mysql:mysql-connector-java:${mysqlVersion}" )'; // eslint-disable-line no-template-curly-in-string + 'runtime ( "mysql:mysql-connector-java:${mysqlVersion}" )'; const mysqlDependency = { group: 'mysql', depGroup: 'mysql', @@ -640,12 +638,12 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('mysqlVersion=7.0.0'); + expect(updatedGradleFile).toBe('mysqlVersion=7.0.0'); }); it('should replace a external property variable assigned to a specific dependency parenthesis syntax', () => { const gradleFile = - "implementation platform(group: 'mysql', name: 'mysql-connector-java', version: mysqlVersion)"; // eslint-disable-line no-template-curly-in-string + "implementation platform(group: 'mysql', name: 'mysql-connector-java', version: mysqlVersion)"; const mysqlDependency = { group: 'mysql', depGroup: 'mysql', @@ -660,7 +658,7 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('mysqlVersion=7.0.0'); + expect(updatedGradleFile).toBe('mysqlVersion=7.0.0'); }); it('should replace a external variable assigned to a map dependency', () => { @@ -682,7 +680,7 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('String mysqlVersion = "7.0.0"'); + expect(updatedGradleFile).toBe('String mysqlVersion = "7.0.0"'); }); it('should replace a external variable assigned to a Kotlin named argument dependency', () => { @@ -704,7 +702,7 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('val mysqlVersion = "7.0.0"'); + expect(updatedGradleFile).toBe('val mysqlVersion = "7.0.0"'); }); it('should replace a external variable assigned to a interpolated dependency', () => { @@ -724,7 +722,7 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('String mysqlVersion = "7.0.0"'); + expect(updatedGradleFile).toBe('String mysqlVersion = "7.0.0"'); }); it('should replace a external extra variable assigned to a Kotlin named argument dependency', () => { @@ -746,7 +744,7 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('val mysqlVersion by extra("7.0.0")'); + expect(updatedGradleFile).toBe('val mysqlVersion by extra("7.0.0")'); }); it('should replace a external lazy extra variable assigned to a Kotlin named argument dependency', () => { @@ -768,7 +766,7 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('val mysqlVersion by extra { "7.0.0" }'); + expect(updatedGradleFile).toBe('val mysqlVersion by extra { "7.0.0" }'); }); it('should replace a external variable assigned to a plugin dependency', () => { @@ -787,6 +785,6 @@ describe('manager/gradle/deep/build-gradle', () => { mysqlDependency, '7.0.0' ); - expect(updatedGradleFile).toEqual('String mysqlVersion = "7.0.0"'); + expect(updatedGradleFile).toBe('String mysqlVersion = "7.0.0"'); }); }); diff --git a/lib/manager/gradle/deep/build-gradle.ts b/lib/manager/gradle/deep/build-gradle.ts index ef68ad46b18cc9..26c40980cee049 100644 --- a/lib/manager/gradle/deep/build-gradle.ts +++ b/lib/manager/gradle/deep/build-gradle.ts @@ -157,8 +157,7 @@ function dependencyStringVariableExpressionFormatMatch( ): RegExp { return regEx( `\\s*dependency\\s+['"]${dependency.group}:${dependency.name}:` + - // eslint-disable-next-line no-template-curly-in-string - '${([^}]*)}' + + '${([^}]*)}' + // eslint-disable-line no-template-curly-in-string `['"](?:\\s|;|})` ); } diff --git a/lib/manager/gradle/deep/gradle-updates-report.spec.ts b/lib/manager/gradle/deep/gradle-updates-report.spec.ts index fc804f22cafb0b..25709740f8f178 100644 --- a/lib/manager/gradle/deep/gradle-updates-report.spec.ts +++ b/lib/manager/gradle/deep/gradle-updates-report.spec.ts @@ -1,7 +1,7 @@ import * as fs from 'fs-extra'; import tmp, { DirectoryResult } from 'tmp-promise'; import * as upath from 'upath'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { exec } from '../../../util/exec'; import { extraEnv } from '../../gradle-wrapper/utils'; import { ifSystemSupportsGradle } from './__testutil__/gradle'; @@ -22,7 +22,7 @@ describe('manager/gradle/deep/gradle-updates-report', () => { beforeEach(async () => { workingDir = await tmp.dir({ unsafeCleanup: true }); - setGlobalConfig({ localDir: workingDir.path }); + GlobalConfig.set({ localDir: workingDir.path }); }); afterEach(() => workingDir.cleanup()); diff --git a/lib/manager/gradle/deep/index-real.spec.ts b/lib/manager/gradle/deep/index-real.spec.ts index d0bfcf894ca144..e6c0fbe321f58c 100644 --- a/lib/manager/gradle/deep/index-real.spec.ts +++ b/lib/manager/gradle/deep/index-real.spec.ts @@ -1,6 +1,6 @@ import fsExtra from 'fs-extra'; import tmp, { DirectoryResult } from 'tmp-promise'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import type { RepoGlobalConfig } from '../../../config/types'; import type { ExtractConfig } from '../../types'; import { ifSystemSupportsGradle } from './__testutil__/gradle'; @@ -26,7 +26,7 @@ describe('manager/gradle/deep/index-real', () => { workingDir = await tmp.dir({ unsafeCleanup: true }); successFile = ''; adminConfig = { localDir: workingDir.path }; - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); testRunConfig = { ...baseConfig }; await fsExtra.copy(`${fixtures}/minimal-project`, workingDir.path); await fsExtra.copy(`${fixtures}/gradle-wrappers/6`, workingDir.path); @@ -48,7 +48,7 @@ allprojects { afterEach(async () => { await workingDir.cleanup(); - setGlobalConfig(); + GlobalConfig.reset(); }); it('executes an executable gradle wrapper', async () => { diff --git a/lib/manager/gradle/deep/index.spec.ts b/lib/manager/gradle/deep/index.spec.ts index 06f3ab781808c1..9177bf06a10f28 100644 --- a/lib/manager/gradle/deep/index.spec.ts +++ b/lib/manager/gradle/deep/index.spec.ts @@ -9,7 +9,7 @@ import { fs, loadFixture, } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import type { RepoGlobalConfig } from '../../../config/types'; import { ReleaseResult, @@ -122,11 +122,11 @@ describe('manager/gradle/deep/index', () => { } beforeAll(() => { - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterAll(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); beforeEach(() => { @@ -304,7 +304,7 @@ describe('manager/gradle/deep/index', () => { }); it('should use docker if required', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); const execSnapshots = setupMocks({ wrapperFilename: null, wrapperPropertiesFilename: null, @@ -321,12 +321,12 @@ describe('manager/gradle/deep/index', () => { packageFile: 'build.gradle', }, ]); - expect(execSnapshots[0].cmd).toEqual('docker pull renovate/java:11.0.12'); + expect(execSnapshots[0].cmd).toBe('docker pull renovate/java:11.0.12'); expect(execSnapshots).toMatchSnapshot(); }); it('should use docker even if gradlew is available', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); const execSnapshots = setupMocks(); getPkgReleases.mockResolvedValueOnce(javaReleases); const dependencies = await extractAllPackageFiles(config, [ @@ -340,12 +340,12 @@ describe('manager/gradle/deep/index', () => { packageFile: 'build.gradle', }, ]); - expect(execSnapshots[0].cmd).toEqual('docker pull renovate/java:11.0.12'); + expect(execSnapshots[0].cmd).toBe('docker pull renovate/java:11.0.12'); expect(execSnapshots).toMatchSnapshot(); }); it('should use docker even if gradlew.bat is available on Windows', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); jest.spyOn(os, 'platform').mockReturnValueOnce('win32'); const execSnapshots = setupMocks({ wrapperFilename: 'gradlew.bat' }); getPkgReleases.mockResolvedValueOnce(javaReleases); @@ -360,7 +360,7 @@ describe('manager/gradle/deep/index', () => { packageFile: 'build.gradle', }, ]); - expect(execSnapshots[0].cmd).toEqual('docker pull renovate/java:11.0.12'); + expect(execSnapshots[0].cmd).toBe('docker pull renovate/java:11.0.12'); expect(execSnapshots).toMatchSnapshot(); }); }); diff --git a/lib/manager/gradle/deep/index.ts b/lib/manager/gradle/deep/index.ts index 85dec5179d1c62..b7e50648aed071 100644 --- a/lib/manager/gradle/deep/index.ts +++ b/lib/manager/gradle/deep/index.ts @@ -1,11 +1,12 @@ import type { Stats } from 'fs'; import upath from 'upath'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import * as datasourceMaven from '../../../datasource/maven'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; -import { ExecOptions, exec } from '../../../util/exec'; +import { exec } from '../../../util/exec'; +import type { ExecOptions } from '../../../util/exec/types'; import { readLocalFile, stat } from '../../../util/fs'; import { extraEnv, @@ -74,8 +75,8 @@ export async function executeGradle( tagConstraint: config.constraints?.java ?? (await getDockerConstraint(gradleRoot)), tagScheme: getJavaVersioning(), - preCommands: await getDockerPreCommands(gradleRoot), }, + preCommands: await getDockerPreCommands(gradleRoot), extraEnv, }; try { @@ -101,7 +102,7 @@ export async function extractAllPackageFiles( ): Promise { let rootBuildGradle: string | undefined; let gradlew: Stats | null; - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); for (const packageFile of packageFiles) { const dirname = upath.dirname(packageFile); const gradlewPath = upath.join(dirname, gradleWrapperFileName()); diff --git a/lib/manager/gradle/deep/utils.ts b/lib/manager/gradle/deep/utils.ts index b984d6c055ab49..8f5d886a182771 100644 --- a/lib/manager/gradle/deep/utils.ts +++ b/lib/manager/gradle/deep/utils.ts @@ -1,5 +1,5 @@ import { join } from 'upath'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { localPathExists, readLocalFile } from '../../../util/fs'; import { extractGradleVersion, @@ -11,7 +11,7 @@ const GradleWrapperProperties = 'gradle/wrapper/gradle-wrapper.properties'; export async function getDockerConstraint( gradleRoot: string ): Promise { - if (getGlobalConfig()?.binarySource !== 'docker') { + if (GlobalConfig.get('binarySource') !== 'docker') { // ignore return null; } @@ -29,7 +29,7 @@ export async function getDockerConstraint( export async function getDockerPreCommands( gradleRoot: string ): Promise { - if (getGlobalConfig()?.binarySource !== 'docker') { + if (GlobalConfig.get('binarySource') !== 'docker') { // ignore return null; } diff --git a/lib/manager/gradle/shallow/__snapshots__/extract.spec.ts.snap b/lib/manager/gradle/shallow/__snapshots__/extract.spec.ts.snap new file mode 100644 index 00000000000000..45b6e6f532bbee --- /dev/null +++ b/lib/manager/gradle/shallow/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manager/gradle/shallow/extract extracts from cross-referenced files 1`] = ` +Array [ + Object { + "datasource": "maven", + "deps": Array [ + Object { + "currentValue": "1.2.3", + "depName": "foo:bar", + "fileReplacePosition": 4, + "groupName": "baz", + "managerData": Object { + "fileReplacePosition": 4, + "packageFile": "gradle.properties", + }, + "registryUrls": Array [ + "https://repo.maven.apache.org/maven2", + "https://example.com", + ], + }, + ], + "packageFile": "gradle.properties", + }, + Object { + "datasource": "maven", + "deps": Array [], + "packageFile": "build.gradle", + }, +] +`; diff --git a/lib/manager/gradle/shallow/__snapshots__/parser.spec.ts.snap b/lib/manager/gradle/shallow/__snapshots__/parser.spec.ts.snap index 4eabcf1f148f9f..16b36d3b357f8b 100644 --- a/lib/manager/gradle/shallow/__snapshots__/parser.spec.ts.snap +++ b/lib/manager/gradle/shallow/__snapshots__/parser.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/gradle/shallow/parser parses fixture from "gradle" manager 1`] = ` +exports[`manager/gradle/shallow/parser calculations parses fixture from "gradle" manager 1`] = ` Array [ Object { "currentValue": "1.5.2.RELEASE", diff --git a/lib/manager/gradle/shallow/extract.spec.ts b/lib/manager/gradle/shallow/extract.spec.ts index 951001c92c74ba..2b4f4751eaeb54 100644 --- a/lib/manager/gradle/shallow/extract.spec.ts +++ b/lib/manager/gradle/shallow/extract.spec.ts @@ -15,7 +15,6 @@ function mockFs(files: Record): void { } describe('manager/gradle/shallow/extract', () => { - beforeAll(() => {}); afterAll(() => { jest.resetAllMocks(); }); @@ -34,78 +33,23 @@ describe('manager/gradle/shallow/extract', () => { expect(res).toBeNull(); }); - it('works', async () => { + it('extracts from cross-referenced files', async () => { mockFs({ 'gradle.properties': 'baz=1.2.3', 'build.gradle': 'url "https://example.com"; "foo:bar:$baz"', - 'settings.gradle': null, }); const res = await extractAllPackageFiles({} as ExtractConfig, [ 'build.gradle', 'gradle.properties', - 'settings.gradle', ]); - expect(res).toMatchObject([ + expect(res).toMatchSnapshot([ { packageFile: 'gradle.properties', - deps: [ - { - depName: 'foo:bar', - currentValue: '1.2.3', - registryUrls: [ - 'https://repo.maven.apache.org/maven2', - 'https://example.com', - ], - }, - ], + deps: [{ depName: 'foo:bar', currentValue: '1.2.3' }], }, { packageFile: 'build.gradle', deps: [] }, - { - datasource: 'maven', - deps: [], - packageFile: 'settings.gradle', - }, - ]); - }); - - it('works with file-ext', async () => { - mockFs({ - 'gradle.properties': '', - 'build.gradle': 'url "https://example.com"; "foo:bar:1.2.3@zip"', - 'settings.gradle': null, - }); - - const res = await extractAllPackageFiles({} as ExtractConfig, [ - 'build.gradle', - 'gradle.properties', - 'settings.gradle', - ]); - - expect(res).toMatchObject([ - { - packageFile: 'gradle.properties', - deps: [], - }, - { - packageFile: 'build.gradle', - deps: [ - { - depName: 'foo:bar', - currentValue: '1.2.3', - registryUrls: [ - 'https://repo.maven.apache.org/maven2', - 'https://example.com', - ], - }, - ], - }, - { - datasource: 'maven', - deps: [], - packageFile: 'settings.gradle', - }, ]); }); @@ -164,12 +108,12 @@ describe('manager/gradle/shallow/extract', () => { }, ], }, - { packageFile: 'build.gradle', deps: [] }, { datasource: 'maven', deps: [], packageFile: 'settings.gradle', }, + { packageFile: 'build.gradle', deps: [] }, ]); }); diff --git a/lib/manager/gradle/shallow/extract.ts b/lib/manager/gradle/shallow/extract.ts index 3ed9208a144266..d233e30f69dba4 100644 --- a/lib/manager/gradle/shallow/extract.ts +++ b/lib/manager/gradle/shallow/extract.ts @@ -46,7 +46,8 @@ export async function extractAllPackageFiles( const registry: VariableRegistry = {}; const packageFilesByName: Record = {}; const registryUrls = []; - for (const packageFile of reorderFiles(packageFiles)) { + const reorderedFiles = reorderFiles(packageFiles); + for (const packageFile of reorderedFiles) { packageFilesByName[packageFile] = { packageFile, datasource, diff --git a/lib/manager/gradle/shallow/parser.spec.ts b/lib/manager/gradle/shallow/parser.spec.ts index c3c439f0af916e..d3b844772bbf73 100644 --- a/lib/manager/gradle/shallow/parser.spec.ts +++ b/lib/manager/gradle/shallow/parser.spec.ts @@ -1,4 +1,5 @@ import { loadFixture } from '../../../../test/util'; +import { SkipReason } from '../../../types'; import { GOOGLE_REPO, GRADLE_PLUGIN_PORTAL_REPO, @@ -12,236 +13,152 @@ describe('manager/gradle/shallow/parser', () => { expect(parseGradle('version = ').deps).toBeEmpty(); expect(parseGradle('id "foo.bar" version').deps).toBeEmpty(); }); - it('parses variables', () => { - let deps; - ({ deps } = parseGradle( - [ - 'version = "1.2.3"', - '"foo:bar_$version:$version"', - 'version = "3.2.1"', - ].join('\n') - )); - expect(deps).toMatchObject([ - { - depName: 'foo:bar_1.2.3', - currentValue: '1.2.3', - }, - ]); - - ({ deps } = parseGradle( - [ - 'set("version", "1.2.3")', - '"foo:bar:$version"', - 'set("version", "3.2.1")', - ].join('\n') - )); - expect(deps).toMatchObject([ - { - depName: 'foo:bar', - currentValue: '1.2.3', - }, - ]); - - ({ deps } = parseGradle('version = "1.2.3"\n"foo:bar:$version@@@"')); - expect(deps).toBeEmpty(); + describe('variable assignments', () => { + test.each` + input | name | value + ${'version = "1.2.3"'} | ${'version'} | ${'1.2.3'} + ${'set("version", "1.2.3")'} | ${'version'} | ${'1.2.3'} + ${'versions.foobar = "1.2.3"'} | ${'versions.foobar'} | ${'1.2.3'} + `('$input', ({ input, name, value }) => { + const { vars } = parseGradle(input); + expect(vars).toContainKey(name); + expect(vars[name]).toMatchObject({ key: name, value }); + }); }); - it('parses registryUrls', () => { - let urls; - - ({ urls } = parseGradle('url ""')); - expect(urls).toBeEmpty(); - - ({ urls } = parseGradle('url "#!@"')); - expect(urls).toBeEmpty(); - - ({ urls } = parseGradle('url "https://example.com"')); - expect(urls).toStrictEqual(['https://example.com']); - - ({ urls } = parseGradle('url("https://example.com")')); - expect(urls).toStrictEqual(['https://example.com']); - - ({ urls } = parseGradle('uri "https://example.com"')); - expect(urls).toStrictEqual(['https://example.com']); - ({ urls } = parseGradle( - 'mavenCentral(); uri("https://example.com"); jcenter(); google(); gradlePluginPortal();' - )); - expect(urls).toStrictEqual([ - MAVEN_REPO, - 'https://example.com', - JCENTER_REPO, - GOOGLE_REPO, - GRADLE_PLUGIN_PORTAL_REPO, - ]); - - ({ urls } = parseGradle( - 'maven("https://repository.mycompany.com/m2/repository")' - )); - expect(urls).toStrictEqual([ - 'https://repository.mycompany.com/m2/repository', - ]); + describe('dependencies', () => { + describe('simple cases', () => { + test.each` + input | output + ${'group: "foo", name: "bar", version: "1.2.3"'} | ${{ 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: depVersion'} | ${null} + ${'("foo", "bar", "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' }} + ${'createXmlValueRemover("defaults", "integer", "integer")'} | ${{ depName: 'defaults:integer', currentValue: 'integer', skipReason: SkipReason.Ignored }} + ${'"foo:bar:1.2.3@zip"'} | ${{ currentValue: '1.2.3', dataType: 'zip', depName: 'foo:bar' }} + `('$input', ({ input, output }) => { + const { deps } = parseGradle(input); + expect(deps).toMatchObject([output].filter(Boolean)); + }); + }); - ({ urls } = parseGradle( - 'maven { url = uri("https://maven.springframework.org/release") }' - )); - expect(urls).toStrictEqual(['https://maven.springframework.org/release']); + describe('variable substitutions', () => { + test.each` + def | str | output + ${'foo = "1.2.3"'} | ${'"foo:bar:$foo@@@"'} | ${null} + ${'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' }} + ${'foo = "1.2.3"'} | ${'"foo:bar_$foo:4.5.6"'} | ${{ depName: 'foo:bar_1.2.3', managerData: { fileReplacePosition: 28 } }} + ${''} | ${'foo.bar = "foo:bar:1.2.3"'} | ${{ depName: 'foo:bar', currentValue: '1.2.3' }} + ${'baz = "1.2.3"'} | ${'foobar = "foo:bar:$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', groupName: 'baz' }} + `('$def | $str', ({ def, str, output }) => { + const input = [def, str].join('\n'); + const { deps } = parseGradle(input); + expect(deps).toMatchObject([output].filter(Boolean)); + }); + }); - ({ urls } = parseGradle( - "maven { url 'https://repository.mycompany.com/m2/repository' }" - )); - expect(urls).toStrictEqual([ - 'https://repository.mycompany.com/m2/repository', - ]); + describe('plugins', () => { + test.each` + input | output + ${'id "foo.bar" version "1.2.3"'} | ${{ depName: 'foo.bar', lookupName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }} + ${'id("foo.bar") version "1.2.3"'} | ${{ depName: 'foo.bar', lookupName: 'foo.bar:foo.bar.gradle.plugin', currentValue: '1.2.3' }} + ${'kotlin("jvm") version "1.3.71"'} | ${{ depName: 'org.jetbrains.kotlin.jvm', lookupName: 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', currentValue: '1.3.71' }} + `('$input', ({ input, output }) => { + const { deps } = parseGradle(input); + expect(deps).toMatchObject([output].filter(Boolean)); + }); + }); }); - it('parses long form deps', () => { - let deps; - ({ deps } = parseGradle( - 'group: "com.example", name: "my.dependency", version: "1.2.3"' - )); - expect(deps).toMatchObject([ - { - depName: 'com.example:my.dependency', - currentValue: '1.2.3', - }, - ]); - - ({ deps } = parseGradle( - "implementation platform(group: 'foo', name: 'bar', version: '1.2.3')" - )); - expect(deps).toMatchObject([ - { - depName: 'foo:bar', - currentValue: '1.2.3', - }, - ]); - - ({ deps } = parseGradle( - 'group: "com.example", name: "my.dependency", version: depVersion' - )); - expect(deps).toBeEmpty(); - - ({ deps } = parseGradle( - 'depVersion = "1.2.3"\ngroup: "com.example", name: "my.dependency", version: depVersion' - )); - expect(deps).toMatchObject([ - { - depName: 'com.example:my.dependency', - currentValue: '1.2.3', - }, - ]); - - ({ deps } = parseGradle('("com.example", "my.dependency", "1.2.3")')); - expect(deps).toMatchObject([ - { - depName: 'com.example:my.dependency', - currentValue: '1.2.3', - }, - ]); - ({ deps } = parseGradle( - '(group = "com.example", name = "my.dependency", version = "1.2.3")' - )); - expect(deps).toMatchObject([ - { - depName: 'com.example:my.dependency', - currentValue: '1.2.3', - }, - ]); + describe('registryUrls', () => { + test.each` + input | url + ${'url ""'} | ${null} + ${'url "#!@"'} | ${null} + ${'url "https://example.com"'} | ${'https://example.com'} + ${'url("https://example.com")'} | ${'https://example.com'} + ${'mavenCentral()'} | ${MAVEN_REPO} + ${'jcenter()'} | ${JCENTER_REPO} + ${'google()'} | ${GOOGLE_REPO} + ${'gradlePluginPortal()'} | ${GRADLE_PLUGIN_PORTAL_REPO} + ${'maven("https://foo.bar/baz/qux")'} | ${'https://foo.bar/baz/qux'} + ${'maven { url = uri("https://foo.bar/baz") }'} | ${'https://foo.bar/baz'} + ${"maven { url 'https://foo.bar/baz' }"} | ${'https://foo.bar/baz'} + `('$input', ({ input, url }) => { + const expected = [url].filter(Boolean); + const { urls } = parseGradle(input); + expect(urls).toStrictEqual(expected); + }); }); - it('parses plugin', () => { - let deps; - - ({ deps } = parseGradle('id "foo.bar" version "1.2.3"')); - expect(deps).toMatchObject([ - { - depName: 'foo.bar', - lookupName: 'foo.bar:foo.bar.gradle.plugin', - currentValue: '1.2.3', - }, - ]); - ({ deps } = parseGradle('id("foo.bar") version "1.2.3"')); - expect(deps).toMatchObject([ - { - depName: 'foo.bar', - lookupName: 'foo.bar:foo.bar.gradle.plugin', - currentValue: '1.2.3', - }, - ]); + describe('calculations', () => { + it('calculates offset', () => { + const content = "'foo:bar:1.2.3'"; + const { deps } = parseGradle(content); + const [res] = deps; + const idx = content + .slice(res.managerData.fileReplacePosition) + .indexOf('1.2.3'); + expect(idx).toBe(0); + }); - ({ deps } = parseGradle('kotlin("jvm") version "1.3.71"')); - expect(deps).toMatchObject([ - { - depName: 'org.jetbrains.kotlin.jvm', - lookupName: - 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', - currentValue: '1.3.71', - }, - ]); - }); - it('parses fixture from "gradle" manager', () => { - const content = loadFixture('build.gradle.example1', '../deep/'); - const { deps } = parseGradle(content, {}, 'build.gradle'); - deps.forEach((dep) => { - expect( - content - .slice(dep.managerData.fileReplacePosition) - .indexOf(dep.currentValue) - ).toEqual(0); + it('parses fixture from "gradle" manager', () => { + const content = loadFixture('build.gradle.example1', '../deep/'); + const { deps } = parseGradle(content, {}, 'build.gradle'); + const replacementIndices = deps.map(({ managerData, currentValue }) => + content.slice(managerData.fileReplacePosition).indexOf(currentValue) + ); + expect(replacementIndices.every((idx) => idx === 0)).toBeTrue(); + expect(deps).toMatchSnapshot(); }); - expect(deps).toMatchSnapshot(); - }); - it('calculates offset', () => { - const content = "'foo:bar:1.2.3'"; - const { deps } = parseGradle(content); - const res = deps[0]; - expect( - content.slice(res.managerData.fileReplacePosition).indexOf('1.2.3') - ).toEqual(0); }); - it('gradle.properties', () => { - expect(parseProps('foo=bar')).toMatchObject({ - vars: { - foo: { - fileReplacePosition: 4, - key: 'foo', - value: 'bar', - }, - }, - deps: [], - }); - expect(parseProps(' foo = bar ')).toMatchObject({ - vars: { - foo: { key: 'foo', value: 'bar', fileReplacePosition: 7 }, - }, - deps: [], + + describe('gradle.properties', () => { + test.each` + input | key | value | fileReplacePosition + ${'foo=bar'} | ${'foo'} | ${'bar'} | ${4} + ${' foo = bar '} | ${'foo'} | ${'bar'} | ${7} + ${'foo.bar=baz'} | ${'foo.bar'} | ${'baz'} | ${8} + `('$input', ({ input, key, value, fileReplacePosition }) => { + expect(parseProps(input)).toMatchObject({ + vars: { [key]: { key, value, fileReplacePosition } }, + }); }); - expect(parseProps('foo.bar=baz')).toMatchObject({ - vars: { - 'foo.bar': { key: 'foo.bar', value: 'baz', fileReplacePosition: 8 }, - }, - deps: [], + + it('handles multi-line file', () => { + expect(parseProps('foo=foo\nbar=bar')).toMatchObject({ + vars: { + foo: { key: 'foo', value: 'foo', fileReplacePosition: 4 }, + bar: { key: 'bar', value: 'bar', fileReplacePosition: 12 }, + }, + deps: [], + }); }); - expect(parseProps('foo=foo\nbar=bar')).toMatchObject({ - vars: { - foo: { key: 'foo', value: 'foo', fileReplacePosition: 4 }, - bar: { key: 'bar', value: 'bar', fileReplacePosition: 12 }, - }, - deps: [], + + it('attaches packageFile', () => { + expect( + parseProps('foo = bar', 'foo/bar/gradle.properties') + ).toMatchObject({ + vars: { foo: { packageFile: 'foo/bar/gradle.properties' } }, + }); }); - expect(parseProps('x=foo:bar:baz', 'x/gradle.properties')).toMatchObject({ - vars: {}, - deps: [ - { - currentValue: 'baz', - depName: 'foo:bar', - managerData: { - fileReplacePosition: 10, - packageFile: 'x/gradle.properties', + + it('parses dependencies', () => { + const res = parseProps('dep = foo:bar:1.2.3'); + + expect(res).toMatchObject({ + deps: [ + { + currentValue: '1.2.3', + depName: 'foo:bar', + managerData: { fileReplacePosition: 14 }, }, - }, - ], + ], + }); }); }); }); diff --git a/lib/manager/gradle/shallow/parser.ts b/lib/manager/gradle/shallow/parser.ts index 6c913197f089b4..f95b2443980fe3 100644 --- a/lib/manager/gradle/shallow/parser.ts +++ b/lib/manager/gradle/shallow/parser.ts @@ -113,14 +113,32 @@ function handleAssignment({ packageFile, tokenMap, }: SyntaxHandlerInput): SyntaxHandlerOutput { - const { keyToken, valToken } = tokenMap; - const variableData: VariableData = { - key: keyToken.value, + const { objectToken, keyToken, valToken } = tokenMap; + const obj = objectToken?.value; + const key = obj ? `${obj}.${keyToken.value}` : keyToken.value; + + const dep = parseDependencyString(valToken.value); + if (dep) { + dep.groupName = key; + dep.managerData = { + fileReplacePosition: valToken.offset + dep.depName.length + 1, + packageFile, + }; + } + + const varData: VariableData = { + key, value: valToken.value, fileReplacePosition: valToken.offset, packageFile, }; - return { vars: { [variableData.key]: variableData } }; + + const result: SyntaxHandlerOutput = { + vars: { [key]: varData }, + deps: dep ? [dep] : [], + }; + + return result; } function processDepString({ @@ -142,6 +160,7 @@ function processDepString({ function processDepInterpolation({ tokenMap, variables, + packageFile: packageFileOrig, }: SyntaxHandlerInput): SyntaxHandlerOutput { const token = tokenMap.depInterpolation as StringInterpolation; const interpolationResult = interpolateString(token.children, variables); @@ -162,8 +181,18 @@ function processDepInterpolation({ } }); if (!dep.managerData) { + const lastToken = token.children[token.children.length - 1]; + if ( + lastToken.type === TokenType.String && + lastToken.value.startsWith(`:${dep.currentValue}`) + ) { + packageFile = packageFileOrig; + fileReplacePosition = lastToken.offset + 1; + delete dep.groupName; + } else { + dep.skipReason = SkipReason.ContainsVariable; + } dep.managerData = { fileReplacePosition, packageFile }; - dep.skipReason = SkipReason.ContainsVariable; } return { deps: [dep] }; } @@ -230,6 +259,8 @@ function processPredefinedRegistryUrl({ return { urls: [registryUrl] }; } +const annoyingMethods = new Set(['createXmlValueRemover']); + function processLongFormDep({ tokenMap, variables, @@ -243,6 +274,7 @@ function processLongFormDep({ const versionToken: Token = tokenMap.version; if (versionToken.type === TokenType.Word) { const variable = variables[versionToken.value]; + dep.groupName = variable.key; dep.managerData = { fileReplacePosition: variable.fileReplacePosition, packageFile: variable.packageFile, @@ -253,12 +285,29 @@ function processLongFormDep({ packageFile, }; } + const methodName = tokenMap.methodName?.value; + if (annoyingMethods.has(methodName)) { + dep.skipReason = SkipReason.Ignored; + } + return { deps: [dep] }; } return null; } const matcherConfigs: SyntaxMatchConfig[] = [ + { + // foo.bar = 'baz' + matchers: [ + { matchType: TokenType.Word, tokenMapKey: 'objectToken' }, + { matchType: TokenType.Dot }, + { matchType: TokenType.Word, tokenMapKey: 'keyToken' }, + { matchType: TokenType.Assignment }, + { matchType: TokenType.String, tokenMapKey: 'valToken' }, + endOfInstruction, + ], + handler: handleAssignment, + }, { // foo = 'bar' matchers: [ @@ -465,6 +514,20 @@ const matcherConfigs: SyntaxMatchConfig[] = [ ], handler: processLongFormDep, }, + { + // fooBarBaz("com.example", "my.dependency", "1.2.3") + matchers: [ + { matchType: TokenType.Word, tokenMapKey: 'methodName' }, + { matchType: TokenType.LeftParen }, + { matchType: potentialStringTypes, tokenMapKey: 'groupId' }, + { matchType: TokenType.Comma }, + { matchType: potentialStringTypes, tokenMapKey: 'artifactId' }, + { matchType: TokenType.Comma }, + { matchType: potentialStringTypes, tokenMapKey: 'version' }, + { matchType: TokenType.RightParen }, + ], + handler: processLongFormDep, + }, { // ("com.example", "my.dependency", "1.2.3") matchers: [ diff --git a/lib/manager/gradle/shallow/tokenizer.spec.ts b/lib/manager/gradle/shallow/tokenizer.spec.ts index 98af7fcb29b6a4..7e4d6bac87f266 100644 --- a/lib/manager/gradle/shallow/tokenizer.spec.ts +++ b/lib/manager/gradle/shallow/tokenizer.spec.ts @@ -112,19 +112,19 @@ describe('manager/gradle/shallow/tokenizer', () => { TokenType.Chars, TokenType.DoubleQuotedFinish, ], - // eslint-disable-next-line no-template-curly-in-string + '"${x}"': [ TokenType.DoubleQuotedStart, TokenType.Variable, TokenType.DoubleQuotedFinish, ], - // eslint-disable-next-line no-template-curly-in-string + '"${foo}"': [ TokenType.DoubleQuotedStart, TokenType.Variable, TokenType.DoubleQuotedFinish, ], - // eslint-disable-next-line no-template-curly-in-string + '"${x()}"': [ TokenType.DoubleQuotedStart, TokenType.IgnoredInterpolationStart, @@ -132,7 +132,7 @@ describe('manager/gradle/shallow/tokenizer', () => { TokenType.RightBrace, TokenType.DoubleQuotedFinish, ], - // eslint-disable-next-line no-template-curly-in-string + '"${x{}}"': [ TokenType.DoubleQuotedStart, TokenType.IgnoredInterpolationStart, @@ -162,7 +162,7 @@ describe('manager/gradle/shallow/tokenizer', () => { children: [{ type: TokenType.Variable }], }, ], - // eslint-disable-next-line no-template-curly-in-string + '" foo ${ bar } baz "': [ { type: TokenType.StringInterpolation, @@ -173,7 +173,7 @@ describe('manager/gradle/shallow/tokenizer', () => { ], }, ], - // eslint-disable-next-line no-template-curly-in-string + '"${ x + y }"': [{ type: TokenType.StringInterpolation, isValid: false }], }; for (const [str, result] of Object.entries(samples)) { diff --git a/lib/manager/gradle/shallow/tokenizer.ts b/lib/manager/gradle/shallow/tokenizer.ts index 728fcbe2a887ca..b599d41f9bd403 100644 --- a/lib/manager/gradle/shallow/tokenizer.ts +++ b/lib/manager/gradle/shallow/tokenizer.ts @@ -3,7 +3,7 @@ import { regEx } from '../../../util/regex'; import { TokenType } from './common'; import type { StringInterpolation, Token } from './types'; -const escapedCharRegex = /\\['"bfnrt\\]/; // TODO #12070 +const escapedCharRegex = /\\['"bfnrt\\]/; // TODO #12870 const escapedChars = { [TokenType.EscapedChar]: { match: escapedCharRegex, @@ -24,17 +24,17 @@ const escapedChars = { const lexer = moo.states({ // Top-level Groovy lexemes main: { - [TokenType.LineComment]: { match: /\/\/.*?$/ }, // TODO #12070 - [TokenType.MultiComment]: { match: /\/\*[^]*?\*\//, lineBreaks: true }, // TODO #12070 - [TokenType.Newline]: { match: /\r?\n/, lineBreaks: true }, // TODO #12070 - [TokenType.Space]: { match: /[ \t\r]+/ }, // TODO #12070 + [TokenType.LineComment]: { match: /\/\/.*?$/ }, // TODO #12870 + [TokenType.MultiComment]: { match: /\/\*[^]*?\*\//, lineBreaks: true }, // TODO #12870 + [TokenType.Newline]: { match: /\r?\n/, lineBreaks: true }, // TODO #12870 + [TokenType.Space]: { match: /[ \t\r]+/ }, // TODO #12870 [TokenType.Semicolon]: ';', [TokenType.Colon]: ':', [TokenType.Dot]: '.', [TokenType.Comma]: ',', - [TokenType.Operator]: /(?:==|\+=?|-=?|\/=?|\*\*?|\.+|:)/, // TODO #12070 + [TokenType.Operator]: /(?:==|\+=?|-=?|\/=?|\*\*?|\.+|:)/, // TODO #12870 [TokenType.Assignment]: '=', - [TokenType.Word]: { match: /[a-zA-Z$_][a-zA-Z0-9$_]+/ }, // TODO #12070 + [TokenType.Word]: { match: /[a-zA-Z$_][a-zA-Z0-9$_]+/ }, // TODO #12870 [TokenType.LeftParen]: { match: '(' }, [TokenType.RightParen]: { match: ')' }, [TokenType.LeftBracket]: { match: '[' }, @@ -86,12 +86,12 @@ const lexer = moo.states({ variable: { // Supported: ${foo}, $foo, ${ foo.bar.baz }, $foo.bar.baz match: - /\${\s*[a-zA-Z_][a-zA-Z0-9_]*(?:\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*)*\s*}|\$[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*/, // TODO #12070 + /\${\s*[a-zA-Z_][a-zA-Z0-9_]*(?:\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*)*\s*}|\$[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*/, // TODO #12870 value: (x: string): string => x.replace(regEx(/^\${?\s*/), '').replace(regEx(/\s*}$/), ''), }, [TokenType.IgnoredInterpolationStart]: { - match: /\${/, // TODO #12070 + match: /\${/, // TODO #12870 push: TokenType.IgnoredInterpolationStart, }, [TokenType.Chars]: moo.fallback, diff --git a/lib/manager/gradle/shallow/update.spec.ts b/lib/manager/gradle/shallow/update.spec.ts index 73033ef1c19eb3..3ef39f28f950e6 100644 --- a/lib/manager/gradle/shallow/update.spec.ts +++ b/lib/manager/gradle/shallow/update.spec.ts @@ -13,7 +13,7 @@ describe('manager/gradle/shallow/update', () => { }, }, }) - ).toEqual('___1.2.4___'); + ).toBe('___1.2.4___'); }); it('groups', () => { @@ -29,7 +29,7 @@ describe('manager/gradle/shallow/update', () => { }, }, }) - ).toEqual('___1.2.5___'); + ).toBe('___1.2.5___'); }); it('returns same content', () => { diff --git a/lib/manager/gradle/shallow/utils.spec.ts b/lib/manager/gradle/shallow/utils.spec.ts index 5dfa16dec5d260..9801792639d115 100644 --- a/lib/manager/gradle/shallow/utils.spec.ts +++ b/lib/manager/gradle/shallow/utils.spec.ts @@ -98,23 +98,41 @@ describe('manager/gradle/shallow/utils', () => { }); it('reorderFiles', () => { - expect(reorderFiles(['a.gradle', 'b.gradle', 'a.gradle'])).toStrictEqual([ + expect( + reorderFiles([ + 'build.gradle', + 'a.gradle', + 'b.gradle', + 'a.gradle', + 'versions.gradle', + ]) + ).toStrictEqual([ + 'versions.gradle', 'a.gradle', 'a.gradle', 'b.gradle', + 'build.gradle', ]); expect( reorderFiles([ 'a/b/c/build.gradle', + 'a/b/versions.gradle', 'a/build.gradle', + 'versions.gradle', 'a/b/build.gradle', + 'a/versions.gradle', 'build.gradle', + 'a/b/c/versions.gradle', ]) ).toStrictEqual([ + 'versions.gradle', 'build.gradle', + 'a/versions.gradle', 'a/build.gradle', + 'a/b/versions.gradle', 'a/b/build.gradle', + 'a/b/c/versions.gradle', 'a/b/c/build.gradle', ]); @@ -146,8 +164,8 @@ describe('manager/gradle/shallow/utils', () => { 'gradle.properties', 'a.gradle', 'b.gradle', - 'build.gradle', 'c.gradle', + 'build.gradle', 'a/gradle.properties', 'a/build.gradle', 'a/b/gradle.properties', diff --git a/lib/manager/gradle/shallow/utils.ts b/lib/manager/gradle/shallow/utils.ts index c9c2a36a795520..cd1a562ccbcd6d 100644 --- a/lib/manager/gradle/shallow/utils.ts +++ b/lib/manager/gradle/shallow/utils.ts @@ -56,14 +56,14 @@ export function parseDependencyString( if (!isDependencyString(input)) { return null; } - const [groupId, artifactId, FullValue] = input?.split(':'); + const [groupId, artifactId, FullValue] = input.split(':'); if (FullValue === versionLikeSubstring(FullValue)) { return { depName: `${groupId}:${artifactId}`, currentValue: FullValue, }; } - const [currentValue, dataType] = FullValue?.split('@'); + const [currentValue, dataType] = FullValue.split('@'); return { depName: `${groupId}:${artifactId}`, currentValue, @@ -95,9 +95,23 @@ export function interpolateString( return resolvedSubstrings.join(''); } +const gradleVersionsFileRegex = regEx('^versions\\.gradle(?:\\.kts)?$', 'i'); +const gradleBuildFileRegex = regEx('^build\\.gradle(?:\\.kts)?$', 'i'); +const gradleFileRegex = regEx('\\.gradle(?:\\.kts)?$', 'i'); + +export function isGradleVersionsFile(path: string): boolean { + const filename = upath.basename(path); + return gradleVersionsFileRegex.test(filename); +} + +export function isGradleBuildFile(path: string): boolean { + const filename = upath.basename(path); + return gradleBuildFileRegex.test(filename); +} + export function isGradleFile(path: string): boolean { - const filename = upath.basename(path).toLowerCase(); - return filename.endsWith('.gradle') || filename.endsWith('.gradle.kts'); + const filename = upath.basename(path); + return gradleFileRegex.test(filename); } export function isPropsFile(path: string): boolean { @@ -114,6 +128,19 @@ export function toAbsolutePath(packageFile: string): string { return upath.join(packageFile.replace(regEx(/^[/\\]*/), '/')); } +function getFileRank(filename: string): number { + if (isPropsFile(filename)) { + return 0; + } + if (isGradleVersionsFile(filename)) { + return 1; + } + if (isGradleBuildFile(filename)) { + return 3; + } + return 2; +} + export function reorderFiles(packageFiles: string[]): string[] { return packageFiles.sort((x, y) => { const xAbs = toAbsolutePath(x); @@ -123,19 +150,18 @@ export function reorderFiles(packageFiles: string[]): string[] { const yDir = upath.dirname(yAbs); if (xDir === yDir) { - if ( - (isGradleFile(xAbs) && isGradleFile(yAbs)) || - (isPropsFile(xAbs) && isPropsFile(yAbs)) - ) { + const xRank = getFileRank(xAbs); + const yRank = getFileRank(yAbs); + if (xRank === yRank) { if (xAbs > yAbs) { return 1; } if (xAbs < yAbs) { return -1; } - } else if (isGradleFile(xAbs)) { + } else if (xRank > yRank) { return 1; - } else if (isGradleFile(yAbs)) { + } else if (yRank > xRank) { return -1; } } else if (xDir.startsWith(yDir)) { diff --git a/lib/manager/helm-requirements/extract.spec.ts b/lib/manager/helm-requirements/extract.spec.ts index 413c939d6181a8..0420e15ff88e5c 100644 --- a/lib/manager/helm-requirements/extract.spec.ts +++ b/lib/manager/helm-requirements/extract.spec.ts @@ -37,7 +37,7 @@ describe('manager/helm-requirements/extract', () => { }); expect(result).not.toBeNull(); expect(result).toMatchSnapshot(); - expect(result.deps.every((dep) => dep.skipReason)).toEqual(true); + expect(result.deps.every((dep) => dep.skipReason)).toBe(true); }); it('parses simple requirements.yaml correctly', () => { fs.readLocalFile.mockResolvedValueOnce(` @@ -111,7 +111,7 @@ describe('manager/helm-requirements/extract', () => { }); expect(result).not.toBeNull(); expect(result).toMatchSnapshot(); - expect(result.deps.every((dep) => dep.skipReason)).toEqual(false); + expect(result.deps.every((dep) => dep.skipReason)).toBe(false); }); it('skips local dependencies', () => { fs.readLocalFile.mockResolvedValueOnce(` diff --git a/lib/manager/helmfile/extract.ts b/lib/manager/helmfile/extract.ts index 39544b7456c5ef..c071b9d2e85534 100644 --- a/lib/manager/helmfile/extract.ts +++ b/lib/manager/helmfile/extract.ts @@ -26,7 +26,7 @@ export function extractPackageFile( } for (const doc of docs) { if (!(doc && is.array(doc.releases))) { - continue; // eslint-disable-line no-continue + continue; } if (doc.repositories) { diff --git a/lib/manager/helmv3/artifacts.spec.ts b/lib/manager/helmv3/artifacts.spec.ts index 8b753abadef754..54238b5cfafba7 100644 --- a/lib/manager/helmv3/artifacts.spec.ts +++ b/lib/manager/helmv3/artifacts.spec.ts @@ -3,7 +3,7 @@ import _fs from 'fs-extra'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as docker from '../../util/exec/docker'; import * as _env from '../../util/exec/env'; @@ -31,11 +31,11 @@ describe('manager/helmv3/artifacts', () => { jest.resetModules(); env.getChildProcessEnv.mockReturnValue(envMock.basic); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null if no Chart.lock found', async () => { const updatedDeps = [{ depName: 'dep1' }]; @@ -109,7 +109,7 @@ describe('manager/helmv3/artifacts', () => { }); it('returns updated Chart.lock with docker', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('Old Chart.lock' as never); const execSnapshots = mockExecAll(exec); fs.readFile.mockResolvedValueOnce('New Chart.lock' as never); @@ -171,7 +171,7 @@ describe('manager/helmv3/artifacts', () => { }); it('sets repositories from aliases with docker', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('Old Chart.lock' as never); const execSnapshots = mockExecAll(exec); fs.readFile.mockResolvedValueOnce('New Chart.lock' as never); diff --git a/lib/manager/helmv3/artifacts.ts b/lib/manager/helmv3/artifacts.ts index 08194441f6ee4d..a2c47cb335786f 100644 --- a/lib/manager/helmv3/artifacts.ts +++ b/lib/manager/helmv3/artifacts.ts @@ -1,7 +1,8 @@ import { quote } from 'shlex'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { getSiblingFileName, getSubDirectory, @@ -81,7 +82,7 @@ export async function updateArtifacts({ if (err.message === TEMPORARY_ERROR) { throw err; } - logger.warn({ err }, 'Failed to update Helm lock file'); + logger.debug({ err }, 'Failed to update Helm lock file'); return [ { artifactError: { diff --git a/lib/manager/helmv3/extract.spec.ts b/lib/manager/helmv3/extract.spec.ts index 3aa609097db193..fdee21c75621cf 100644 --- a/lib/manager/helmv3/extract.spec.ts +++ b/lib/manager/helmv3/extract.spec.ts @@ -35,7 +35,7 @@ describe('manager/helmv3/extract', () => { }); expect(result).not.toBeNull(); expect(result).toMatchSnapshot(); - expect(result.deps.every((dep) => dep.skipReason)).toEqual(true); + expect(result.deps.every((dep) => dep.skipReason)).toBe(true); }); it('parses simple Chart.yaml correctly', async () => { const content = ` @@ -129,7 +129,7 @@ describe('manager/helmv3/extract', () => { }); expect(result).not.toBeNull(); expect(result).toMatchSnapshot(); - expect(result.deps.every((dep) => dep.skipReason)).toEqual(false); + expect(result.deps.every((dep) => dep.skipReason)).toBe(false); }); it("doesn't fail if Chart.yaml is invalid", async () => { const content = ` diff --git a/lib/manager/helmv3/update.ts b/lib/manager/helmv3/update.ts index 6f02c15eb37d5c..a965beb3221aac 100644 --- a/lib/manager/helmv3/update.ts +++ b/lib/manager/helmv3/update.ts @@ -1,5 +1,6 @@ import { ReleaseType, inc } from 'semver'; import { logger } from '../../logger'; +import { regEx } from '../../util/regex'; import type { BumpPackageVersionResult } from '../types'; export function bumpPackageVersion( @@ -20,7 +21,7 @@ export function bumpPackageVersion( } logger.debug({ newChartVersion }); bumpedContent = content.replace( - /^(?version:\s*).*$/m, // TODO #12070 + regEx(`^(?version:\\s*).*$`, 'm'), `$${newChartVersion}` ); if (bumpedContent === content) { diff --git a/lib/manager/jenkins/extract.ts b/lib/manager/jenkins/extract.ts index c5e9d013eb21cc..4b13b92bec913d 100644 --- a/lib/manager/jenkins/extract.ts +++ b/lib/manager/jenkins/extract.ts @@ -1,5 +1,6 @@ +import is from '@sindresorhus/is'; import { load } from 'js-yaml'; -import * as datasourceJenkins from '../../datasource/jenkins-plugins'; +import { JenkinsPluginsDatasource } from '../../datasource/jenkins-plugins'; import { logger } from '../../logger'; import { SkipReason } from '../../types'; import { isSkipComment } from '../../util/ignore'; @@ -12,7 +13,7 @@ const YamlExtension = regEx(/\.ya?ml$/); function getDependency(plugin: JenkinsPlugin): PackageDependency { const dep: PackageDependency = { - datasource: datasourceJenkins.id, + datasource: JenkinsPluginsDatasource.id, versioning: dockerVersioning.id, depName: plugin.artifactId, }; @@ -55,7 +56,7 @@ function extractYaml(content: string): PackageDependency[] { try { const doc = load(content, { json: true }) as JenkinsPlugins; - if (doc?.plugins) { + if (is.nonEmptyArray(doc?.plugins)) { for (const plugin of doc.plugins) { if (plugin.artifactId) { const dep = getDependency(plugin); diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-local-dependencies.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-local-dependencies.json new file mode 100644 index 00000000000000..f96725fc11b520 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-local-dependencies.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "dependencies": [ + { + "source": { + "local": { + "directory": "jsonnet" + } + }, + "version": "" + } + ], + "legacyImports": true +} + diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-no-dependencies.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-no-dependencies.json new file mode 100644 index 00000000000000..4388812ca21433 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-no-dependencies.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "dependencies": [], + "legacyImports": true +} diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-with-name.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-with-name.json new file mode 100644 index 00000000000000..1294d50ee63cd9 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-with-name.json @@ -0,0 +1,16 @@ +{ + "version": 1, + "dependencies": [ + { + "source": { + "git": { + "remote": "https://github.com/prometheus-operator/prometheus-operator", + "subdir": "jsonnet/mixin" + } + }, + "version": "v0.50.0", + "name": "prometheus-operator-mixin" + } + ], + "legacyImports": true +} diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile.json new file mode 100644 index 00000000000000..09fcb116706bfb --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "dependencies": [ + { + "source": { + "git": { + "remote": "https://github.com/prometheus-operator/prometheus-operator.git", + "subdir": "jsonnet/prometheus-operator" + } + }, + "version": "v0.50.0" + }, + { + "source": { + "git": { + "remote": "ssh://git@github.com/prometheus-operator/kube-prometheus.git", + "subdir": "jsonnet/kube-prometheus" + } + }, + "version": "v0.9.0" + } + ], + "legacyImports": true +} diff --git a/lib/manager/jsonnet-bundler/__snapshots__/artifacts.spec.ts.snap b/lib/manager/jsonnet-bundler/__snapshots__/artifacts.spec.ts.snap new file mode 100644 index 00000000000000..79d70146d35e51 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__snapshots__/artifacts.spec.ts.snap @@ -0,0 +1,129 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manager/jsonnet-bundler/artifacts performs lock file maintenance 1`] = ` +Array [ + Object { + "file": Object { + "contents": "Updated jsonnetfile.lock.json", + "name": "jsonnetfile.lock.json", + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts performs lock file maintenance 2`] = ` +Array [ + Object { + "cmd": "jb update", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts returns error when jb update fails 1`] = ` +Array [ + Object { + "artifactError": Object { + "lockFile": "jsonnetfile.lock.json", + "stderr": "jb released the magic smoke", + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts returns error when jb update fails 2`] = ` +Array [ + Object { + "cmd": "jb update", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts returns null if there are no changes 1`] = `Array []`; + +exports[`manager/jsonnet-bundler/artifacts updates the vendor dir when dependencies change 1`] = ` +Array [ + Object { + "file": Object { + "contents": "Updated jsonnetfile.json", + "name": "jsonnetfile.json", + }, + }, + Object { + "file": Object { + "contents": "Updated jsonnetfile.lock.json", + "name": "jsonnetfile.lock.json", + }, + }, + Object { + "file": Object { + "contents": "New foo/main.jsonnet", + "name": "vendor/foo/main.jsonnet", + }, + }, + Object { + "file": Object { + "contents": "New bar/main.jsonnet", + "name": "vendor/bar/main.jsonnet", + }, + }, + Object { + "file": Object { + "contents": "vendor/baz/deleted.jsonnet", + "name": "|delete|", + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts updates the vendor dir when dependencies change 2`] = ` +Array [ + Object { + "cmd": "jb update https://github.com/foo/foo.git ssh://git@github.com/foo/foo.git/bar", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; diff --git a/lib/manager/jsonnet-bundler/__snapshots__/extract.spec.ts.snap b/lib/manager/jsonnet-bundler/__snapshots__/extract.spec.ts.snap new file mode 100644 index 00000000000000..2c3bb91b3dd6db --- /dev/null +++ b/lib/manager/jsonnet-bundler/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manager/jsonnet-bundler/extract extractPackageFile() extracts dependency 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.50.0", + "depName": "prometheus-operator", + "lookupName": "https://github.com/prometheus-operator/prometheus-operator.git", + "managerData": Object { + "subdir": "jsonnet/prometheus-operator", + }, + }, + Object { + "currentValue": "v0.9.0", + "depName": "kube-prometheus", + "lookupName": "ssh://git@github.com/prometheus-operator/kube-prometheus.git", + "managerData": Object { + "subdir": "jsonnet/kube-prometheus", + }, + }, + ], +} +`; + +exports[`manager/jsonnet-bundler/extract extractPackageFile() extracts dependency with custom name 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.50.0", + "depName": "prometheus-operator-mixin", + "lookupName": "https://github.com/prometheus-operator/prometheus-operator", + "managerData": Object { + "subdir": "jsonnet/mixin", + }, + }, + ], +} +`; diff --git a/lib/manager/jsonnet-bundler/artifacts.spec.ts b/lib/manager/jsonnet-bundler/artifacts.spec.ts new file mode 100644 index 00000000000000..87c666493499b2 --- /dev/null +++ b/lib/manager/jsonnet-bundler/artifacts.spec.ts @@ -0,0 +1,196 @@ +import { join } from 'upath'; +import { envMock, exec, mockExecAll } from '../../../test/exec-util'; +import { env, fs, git } from '../../../test/util'; +import { GlobalConfig } from '../../config/global'; +import type { RepoGlobalConfig } from '../../config/types'; +import type { StatusResult } from '../../util/git/types'; +import type { UpdateArtifactsConfig } from '../types'; +import { updateArtifacts } from '.'; + +jest.mock('child_process'); +jest.mock('../../util/exec/env'); +jest.mock('../../util/fs'); +jest.mock('../../util/git'); + +const adminConfig: RepoGlobalConfig = { + // `join` fixes Windows CI + localDir: join('/tmp/github/some/repo'), + cacheDir: join('/tmp/renovate/cache'), +}; +const config: UpdateArtifactsConfig = {}; + +describe('manager/jsonnet-bundler/artifacts', () => { + beforeEach(() => { + env.getChildProcessEnv.mockReturnValue(envMock.basic); + + GlobalConfig.set(adminConfig); + }); + + it('returns null if jsonnetfile.lock does not exist', async () => { + fs.readLocalFile.mockResolvedValueOnce(null); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config, + }) + ).toBeNull(); + }); + + it('returns null if there are no changes', async () => { + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: [], + not_added: [], + deleted: [], + isClean(): boolean { + return true; + }, + } as StatusResult); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config, + }) + ).toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('updates the vendor dir when dependencies change', async () => { + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + not_added: ['vendor/foo/main.jsonnet', 'vendor/bar/main.jsonnet'], + modified: ['jsonnetfile.json', 'jsonnetfile.lock.json'], + deleted: ['vendor/baz/deleted.jsonnet'], + isClean(): boolean { + return false; + }, + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.json'); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.lock.json'); + fs.readLocalFile.mockResolvedValueOnce('New foo/main.jsonnet'); + fs.readLocalFile.mockResolvedValueOnce('New bar/main.jsonnet'); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [ + { + depName: 'foo', + lookupName: 'https://github.com/foo/foo.git', + }, + { + depName: 'foo', + lookupName: 'ssh://git@github.com/foo/foo.git', + managerData: { + subdir: 'bar', + }, + }, + ], + newPackageFileContent: 'Updated jsonnetfile.json', + config, + }) + ).toMatchSnapshot([ + { + file: { + name: 'jsonnetfile.json', + contents: 'Updated jsonnetfile.json', + }, + }, + { + file: { + name: 'jsonnetfile.lock.json', + contents: 'Updated jsonnetfile.lock.json', + }, + }, + { + file: { + name: 'vendor/foo/main.jsonnet', + contents: 'New foo/main.jsonnet', + }, + }, + { + file: { + name: 'vendor/bar/main.jsonnet', + contents: 'New bar/main.jsonnet', + }, + }, + { + file: { + name: '|delete|', + contents: 'vendor/baz/deleted.jsonnet', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('performs lock file maintenance', async () => { + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['jsonnetfile.lock.json'], + isClean(): boolean { + return false; + }, + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.lock.json'); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchSnapshot([ + { + file: { + name: 'jsonnetfile.lock.json', + contents: 'Updated jsonnetfile.lock.json', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('returns error when jb update fails', async () => { + const execError = new Error(); + (execError as any).stderr = 'jb released the magic smoke'; + + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec, execError); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['jsonnetfile.lock.json'], + isClean(): boolean { + return false; + }, + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.lock.json'); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchSnapshot([ + { + artifactError: { + lockFile: 'jsonnetfile.lock.json', + stderr: 'jb released the magic smoke', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); +}); diff --git a/lib/manager/jsonnet-bundler/artifacts.ts b/lib/manager/jsonnet-bundler/artifacts.ts new file mode 100644 index 00000000000000..9e19bc5701683f --- /dev/null +++ b/lib/manager/jsonnet-bundler/artifacts.ts @@ -0,0 +1,110 @@ +import { quote } from 'shlex'; +import { TEMPORARY_ERROR } from '../../constants/error-messages'; +import { logger } from '../../logger'; +import { exec } from '../../util/exec'; +import type { ExecOptions, ToolConstraint } from '../../util/exec/types'; +import { readLocalFile } from '../../util/fs'; +import { getRepoStatus } from '../../util/git'; +import { regEx } from '../../util/regex'; +import type { + PackageDependency, + UpdateArtifact, + UpdateArtifactsResult, +} from '../types'; + +function dependencyUrl(dep: PackageDependency): string { + const url = dep.lookupName; + if (dep.managerData?.subdir) { + return url.concat('/', dep.managerData.subdir); + } + return url; +} + +export async function updateArtifacts( + updateArtifact: UpdateArtifact +): Promise { + const { packageFileName, updatedDeps, config } = updateArtifact; + logger.trace({ packageFileName }, 'jsonnet-bundler.updateArtifacts()'); + + const lockFileName = packageFileName.replace(regEx(/\.json$/), '.lock.json'); + const existingLockFileContent = await readLocalFile(lockFileName, 'utf8'); + + if (!existingLockFileContent) { + logger.debug('No jsonnetfile.lock.json found'); + return null; + } + + const jsonnetBundlerToolConstraint: ToolConstraint = { + toolName: 'jb', + constraint: config.constraints?.jb, + }; + + const execOptions: ExecOptions = { + cwdFile: packageFileName, + docker: { + image: 'sidecar', + }, + toolConstraints: [jsonnetBundlerToolConstraint], + }; + + try { + if (config.isLockFileMaintenance) { + await exec('jb update', execOptions); + } else { + const dependencyUrls = updatedDeps.map(dependencyUrl); + if (dependencyUrls.length > 0) { + await exec( + `jb update ${dependencyUrls.map(quote).join(' ')}`, + execOptions + ); + } + } + + const status = await getRepoStatus(); + + if (status.isClean()) { + return null; + } + + const res: UpdateArtifactsResult[] = []; + + for (const f of status.modified ?? []) { + res.push({ + file: { + name: f, + contents: await readLocalFile(f), + }, + }); + } + for (const f of status.not_added ?? []) { + res.push({ + file: { + name: f, + contents: await readLocalFile(f), + }, + }); + } + for (const f of status.deleted ?? []) { + res.push({ + file: { + name: '|delete|', + contents: f, + }, + }); + } + + return res; + } catch (err) /* istanbul ignore next */ { + if (err.message === TEMPORARY_ERROR) { + throw err; + } + return [ + { + artifactError: { + lockFile: lockFileName, + stderr: err.stderr, + }, + }, + ]; + } +} diff --git a/lib/manager/jsonnet-bundler/extract.spec.ts b/lib/manager/jsonnet-bundler/extract.spec.ts new file mode 100644 index 00000000000000..7264679f2e116e --- /dev/null +++ b/lib/manager/jsonnet-bundler/extract.spec.ts @@ -0,0 +1,68 @@ +import { loadFixture } from '../../../test/util'; +import { extractPackageFile } from '.'; + +const jsonnetfile = loadFixture('jsonnetfile.json'); +const jsonnetfileWithName = loadFixture('jsonnetfile-with-name.json'); +const jsonnetfileNoDependencies = loadFixture( + 'jsonnetfile-no-dependencies.json' +); +const jsonnetfileLocalDependencies = loadFixture( + 'jsonnetfile-local-dependencies.json' +); + +describe('manager/jsonnet-bundler/extract', () => { + describe('extractPackageFile()', () => { + it('returns null for invalid jsonnetfile', () => { + expect( + extractPackageFile('this is not a jsonnetfile', 'jsonnetfile.json') + ).toBeNull(); + }); + it('returns null for jsonnetfile with no dependencies', () => { + expect( + extractPackageFile(jsonnetfileNoDependencies, 'jsonnetfile.json') + ).toBeNull(); + }); + it('returns null for local dependencies', () => { + expect( + extractPackageFile(jsonnetfileLocalDependencies, 'jsonnetfile.json') + ).toBeNull(); + }); + it('returns null for vendored dependencies', () => { + expect( + extractPackageFile(jsonnetfile, 'vendor/jsonnetfile.json') + ).toBeNull(); + }); + it('extracts dependency', () => { + const res = extractPackageFile(jsonnetfile, 'jsonnetfile.json'); + expect(res).toMatchSnapshot({ + deps: [ + { + depName: 'prometheus-operator', + lookupName: + 'https://github.com/prometheus-operator/prometheus-operator.git', + currentValue: 'v0.50.0', + }, + { + depName: 'kube-prometheus', + lookupName: + 'ssh://git@github.com/prometheus-operator/kube-prometheus.git', + currentValue: 'v0.9.0', + }, + ], + }); + }); + it('extracts dependency with custom name', () => { + const res = extractPackageFile(jsonnetfileWithName, 'jsonnetfile.json'); + expect(res).toMatchSnapshot({ + deps: [ + { + depName: 'prometheus-operator-mixin', + lookupName: + 'https://github.com/prometheus-operator/prometheus-operator', + currentValue: 'v0.50.0', + }, + ], + }); + }); + }); +}); diff --git a/lib/manager/jsonnet-bundler/extract.ts b/lib/manager/jsonnet-bundler/extract.ts new file mode 100644 index 00000000000000..7162f4cd8873be --- /dev/null +++ b/lib/manager/jsonnet-bundler/extract.ts @@ -0,0 +1,57 @@ +import { logger } from '../../logger'; +import { regEx } from '../../util/regex'; +import type { PackageDependency, PackageFile } from '../types'; +import type { Dependency, JsonnetFile } from './types'; + +const gitUrl = regEx( + /(ssh:\/\/git@|https:\/\/)([\w.]+)\/([\w:/\-~]*)\/(?[\w:/-]+)(\.git)?/ +); + +export function extractPackageFile( + content: string, + packageFile: string +): PackageFile | null { + logger.trace({ packageFile }, 'jsonnet-bundler.extractPackageFile()'); + + if (packageFile.match(/vendor\//)) { + return null; + } + + const deps: PackageDependency[] = []; + let jsonnetFile: JsonnetFile; + try { + jsonnetFile = JSON.parse(content) as JsonnetFile; + } catch (err) { + logger.debug({ packageFile }, 'Invalid JSON'); + return null; + } + + for (const dependency of jsonnetFile.dependencies ?? []) { + const dep = extractDependency(dependency); + if (dep) { + deps.push(dep); + } + } + + if (!deps.length) { + return null; + } + + return { deps }; +} + +function extractDependency(dependency: Dependency): PackageDependency | null { + if (!dependency.source.git) { + return null; + } + + const match = gitUrl.exec(dependency.source.git.remote); + + return { + depName: + dependency.name || match.groups.depName || dependency.source.git.remote, + lookupName: dependency.source.git.remote, + currentValue: dependency.version, + managerData: { subdir: dependency.source.git.subdir }, + }; +} diff --git a/lib/manager/jsonnet-bundler/index.ts b/lib/manager/jsonnet-bundler/index.ts new file mode 100644 index 00000000000000..aa3fd5480c9944 --- /dev/null +++ b/lib/manager/jsonnet-bundler/index.ts @@ -0,0 +1,10 @@ +import { GitTagsDatasource } from '../../datasource/git-tags'; +export { updateArtifacts } from './artifacts'; +export { extractPackageFile } from './extract'; + +export const supportsLockFileMaintenance = true; + +export const defaultConfig = { + fileMatch: ['(^|/)jsonnetfile.json$'], + datasource: GitTagsDatasource.id, +}; diff --git a/lib/manager/jsonnet-bundler/readme.md b/lib/manager/jsonnet-bundler/readme.md new file mode 100644 index 00000000000000..7a8d7da5201248 --- /dev/null +++ b/lib/manager/jsonnet-bundler/readme.md @@ -0,0 +1,5 @@ +Extracts dependencies from `jsonnetfile.json` files, updates `jsonnetfile.lock.json` and updates the `vendor` directory. + +Supports [lock file maintenance](https://docs.renovatebot.com/configuration-options/#lockfilemaintenance). + +This plugin requires `jsonnet-bundler >= v0.4.0` since previous versions don't support updating single dependencies. diff --git a/lib/manager/jsonnet-bundler/types.ts b/lib/manager/jsonnet-bundler/types.ts new file mode 100644 index 00000000000000..9947e87b4d6ec5 --- /dev/null +++ b/lib/manager/jsonnet-bundler/types.ts @@ -0,0 +1,20 @@ +// original spec https://github.com/jsonnet-bundler/jsonnet-bundler/tree/master/spec/v1 + +export interface JsonnetFile { + dependencies?: Dependency[]; +} + +export interface Dependency { + source: Source; + version: string; + name?: string; +} + +export interface Source { + git?: GitSource; +} + +export interface GitSource { + remote: string; + subdir?: string; +} diff --git a/lib/manager/kustomize/__fixtures__/kustomizeHelmChart.yaml b/lib/manager/kustomize/__fixtures__/kustomizeHelmChart.yaml new file mode 100644 index 00000000000000..56f4887e4ac969 --- /dev/null +++ b/lib/manager/kustomize/__fixtures__/kustomizeHelmChart.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +helmCharts: +- name: minecraft + includeCRDs: false + valuesInline: + minecraftServer: + eula: true + difficulty: hard + rcon: + enabled: true + releaseName: moria + version: 3.1.3 + repo: https://itzg.github.io/minecraft-server-charts diff --git a/lib/manager/kustomize/__snapshots__/extract.spec.ts.snap b/lib/manager/kustomize/__snapshots__/extract.spec.ts.snap index 4d78020fb53a6b..945e568313cb06 100644 --- a/lib/manager/kustomize/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/kustomize/__snapshots__/extract.spec.ts.snap @@ -160,6 +160,22 @@ Array [ ] `; +exports[`manager/kustomize/extract extractPackageFile() parses helmChart field 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "3.1.3", + "datasource": "helm", + "depName": "minecraft", + "depType": "HelmChart", + "registryUrls": Array [ + "https://itzg.github.io/minecraft-server-charts", + ], + }, + ], +} +`; + exports[`manager/kustomize/extract extractPackageFile() should extract bases resources and components from their respective blocks 1`] = ` Array [ Object { diff --git a/lib/manager/kustomize/extract.spec.ts b/lib/manager/kustomize/extract.spec.ts index 265d0cc3d6888d..56c99b733d37d0 100644 --- a/lib/manager/kustomize/extract.spec.ts +++ b/lib/manager/kustomize/extract.spec.ts @@ -2,11 +2,13 @@ import { loadFixture } from '../../../test/util'; import * as datasourceDocker from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import * as datasourceGitHubTags from '../../datasource/github-tags'; +import { HelmDatasource } from '../../datasource/helm'; import { SkipReason } from '../../types'; import { - extractBase, + extractHelmChart, extractImage, extractPackageFile, + extractResource, parseKustomize, } from './extract'; @@ -22,6 +24,7 @@ const kustomizeComponent = loadFixture('component.yaml'); const newTag = loadFixture('newTag.yaml'); const newName = loadFixture('newName.yaml'); const digest = loadFixture('digest.yaml'); +const kustomizeHelmChart = loadFixture('kustomizeHelmChart.yaml'); describe('manager/kustomize/extract', () => { it('should successfully parse a valid kustomize file', () => { @@ -34,7 +37,7 @@ describe('manager/kustomize/extract', () => { }); describe('extractBase', () => { it('should return null for a local base', () => { - const res = extractBase('./service-1'); + const res = extractResource('./service-1'); expect(res).toBeNull(); }); it('should extract out the version of an http base', () => { @@ -46,11 +49,11 @@ describe('manager/kustomize/extract', () => { depName: 'user/test-repo', }; - const pkg = extractBase(`${base}?ref=${version}`); + const pkg = extractResource(`${base}?ref=${version}`); expect(pkg).toEqual(sample); }); it('should extract the version of a non http base', () => { - const pkg = extractBase( + const pkg = extractResource( 'ssh://git@bitbucket.com/user/test-repo?ref=v1.2.3' ); expect(pkg).toEqual({ @@ -61,7 +64,7 @@ describe('manager/kustomize/extract', () => { }); }); it('should extract the depName if the URL includes a port number', () => { - const pkg = extractBase( + const pkg = extractResource( 'ssh://git@bitbucket.com:7999/user/test-repo?ref=v1.2.3' ); expect(pkg).toEqual({ @@ -72,7 +75,7 @@ describe('manager/kustomize/extract', () => { }); }); it('should extract the version of a non http base with subdir', () => { - const pkg = extractBase( + const pkg = extractResource( 'ssh://git@bitbucket.com/user/test-repo/subdir?ref=v1.2.3' ); expect(pkg).toEqual({ @@ -91,7 +94,7 @@ describe('manager/kustomize/extract', () => { depName: 'fluxcd/flux', }; - const pkg = extractBase(`${base}?ref=${version}`); + const pkg = extractResource(`${base}?ref=${version}`); expect(pkg).toEqual(sample); }); it('should extract out the version of a git base', () => { @@ -103,7 +106,7 @@ describe('manager/kustomize/extract', () => { depName: 'user/repo', }; - const pkg = extractBase(`${base}?ref=${version}`); + const pkg = extractResource(`${base}?ref=${version}`); expect(pkg).toEqual(sample); }); it('should extract out the version of a git base with subdir', () => { @@ -115,7 +118,32 @@ describe('manager/kustomize/extract', () => { depName: 'user/repo', }; - const pkg = extractBase(`${base}?ref=${version}`); + const pkg = extractResource(`${base}?ref=${version}`); + expect(pkg).toEqual(sample); + }); + }); + describe('extractHelmChart', () => { + it('should return null on a null input', () => { + const pkg = extractHelmChart({ + name: null, + repo: null, + version: null, + }); + expect(pkg).toBeNull(); + }); + it('should correctly extract a chart', () => { + const registryUrl = 'https://docs.renovatebot.com/helm-charts'; + const sample = { + depName: 'renovate', + currentValue: '29.6.0', + registryUrls: [registryUrl], + datasource: HelmDatasource.id, + }; + const pkg = extractHelmChart({ + name: sample.depName, + version: sample.currentValue, + repo: registryUrl, + }); expect(pkg).toEqual(sample); }); }); @@ -221,16 +249,16 @@ describe('manager/kustomize/extract', () => { const res = extractPackageFile(kustomizeHTTP); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(2); - expect(res.deps[0].currentValue).toEqual('v0.0.1'); - expect(res.deps[1].currentValue).toEqual('1.19.0'); - expect(res.deps[1].depName).toEqual('fluxcd/flux'); + expect(res.deps[0].currentValue).toBe('v0.0.1'); + expect(res.deps[1].currentValue).toBe('1.19.0'); + expect(res.deps[1].depName).toBe('fluxcd/flux'); }); it('should extract out image versions', () => { const res = extractPackageFile(gitImages); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(6); - expect(res.deps[0].currentValue).toEqual('v0.1.0'); - expect(res.deps[1].currentValue).toEqual('v0.0.1'); + expect(res.deps[0].currentValue).toBe('v0.1.0'); + expect(res.deps[1].currentValue).toBe('v0.0.1'); expect(res.deps[5].skipReason).toEqual(SkipReason.InvalidValue); }); it('ignores non-Kubernetes empty files', () => { @@ -244,28 +272,30 @@ describe('manager/kustomize/extract', () => { expect(res).not.toBeNull(); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); - expect(res.deps[0].currentValue).toEqual('v0.0.1'); - expect(res.deps[1].currentValue).toEqual('1.19.0'); - expect(res.deps[2].currentValue).toEqual('1.18.0'); - expect(res.deps[1].depName).toEqual('fluxcd/flux'); - expect(res.deps[2].depName).toEqual('fluxcd/flux'); - expect(res.deps[0].depType).toEqual('Kustomization'); - expect(res.deps[1].depType).toEqual('Kustomization'); - expect(res.deps[2].depType).toEqual('Kustomization'); + expect(res.deps[0].currentValue).toBe('v0.0.1'); + expect(res.deps[1].currentValue).toBe('1.19.0'); + expect(res.deps[2].currentValue).toBe('1.18.0'); + expect(res.deps[0].depName).toBe('moredhel/remote-kustomize'); + expect(res.deps[1].depName).toBe('fluxcd/flux'); + expect(res.deps[2].depName).toBe('fluxcd/flux'); + expect(res.deps[0].depType).toBe('Kustomization'); + expect(res.deps[1].depType).toBe('Kustomization'); + expect(res.deps[2].depType).toBe('Kustomization'); }); it('should extract dependencies when kind is Component', () => { const res = extractPackageFile(kustomizeComponent); expect(res).not.toBeNull(); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); - expect(res.deps[0].currentValue).toEqual('1.19.0'); - expect(res.deps[1].currentValue).toEqual('1.18.0'); - expect(res.deps[2].currentValue).toEqual('v0.1.0'); - expect(res.deps[1].depName).toEqual('fluxcd/flux'); - expect(res.deps[2].depName).toEqual('node'); - expect(res.deps[0].depType).toEqual('Component'); - expect(res.deps[1].depType).toEqual('Component'); - expect(res.deps[2].depType).toEqual('Component'); + expect(res.deps[0].currentValue).toBe('1.19.0'); + expect(res.deps[1].currentValue).toBe('1.18.0'); + expect(res.deps[2].currentValue).toBe('v0.1.0'); + expect(res.deps[0].depName).toBe('fluxcd/flux'); + expect(res.deps[1].depName).toBe('fluxcd/flux'); + expect(res.deps[2].depName).toBe('node'); + expect(res.deps[0].depType).toBe('Component'); + expect(res.deps[1].depType).toBe('Component'); + expect(res.deps[2].depType).toBe('Component'); }); const postgresDigest = @@ -344,5 +374,19 @@ describe('manager/kustomize/extract', () => { ], }); }); + + it('parses helmChart field', () => { + const res = extractPackageFile(kustomizeHelmChart); + expect(res).toMatchSnapshot({ + deps: [ + { + depType: 'HelmChart', + depName: 'minecraft', + currentValue: '3.1.3', + registryUrls: ['https://itzg.github.io/minecraft-server-charts'], + }, + ], + }); + }); }); }); diff --git a/lib/manager/kustomize/extract.ts b/lib/manager/kustomize/extract.ts index f2c223d8e98706..df09e194ee8bcd 100644 --- a/lib/manager/kustomize/extract.ts +++ b/lib/manager/kustomize/extract.ts @@ -3,12 +3,13 @@ import { load } from 'js-yaml'; import * as datasourceDocker from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import * as datasourceGitHubTags from '../../datasource/github-tags'; +import { HelmDatasource } from '../../datasource/helm'; import { logger } from '../../logger'; import { SkipReason } from '../../types'; import { regEx } from '../../util/regex'; import { splitImageParts } from '../dockerfile/extract'; import type { PackageDependency, PackageFile } from '../types'; -import type { Image, Kustomize } from './types'; +import type { HelmChart, Image, Kustomize } from './types'; // URL specifications should follow the hashicorp URL format // https://github.com/hashicorp/go-getter#url-format @@ -16,7 +17,7 @@ const gitUrl = regEx( /^(?:git::)?(?(?:(?:(?:http|https|ssh):\/\/)?(?:.*@)?)?(?(?:[^:/\s]+(?::[0-9]+)?[:/])?(?[^/\s]+\/[^/\s]+)))(?[^?\s]*)\?ref=(?.+)$/ ); -export function extractBase(base: string): PackageDependency | null { +export function extractResource(base: string): PackageDependency | null { const match = gitUrl.exec(base); if (!match) { @@ -106,10 +107,25 @@ export function extractImage(image: Image): PackageDependency | null { return null; } +export function extractHelmChart( + helmChart: HelmChart +): PackageDependency | null { + if (!helmChart.name) { + return null; + } + + return { + depName: helmChart.name, + currentValue: helmChart.version, + registryUrls: [helmChart.repo], + datasource: HelmDatasource.id, + }; +} + export function parseKustomize(content: string): Kustomize | null { - let pkg = null; + let pkg: Kustomize | null = null; try { - pkg = load(content, { json: true }); + pkg = load(content, { json: true }) as Kustomize; } catch (e) /* istanbul ignore next */ { return null; } @@ -122,12 +138,6 @@ export function parseKustomize(content: string): Kustomize | null { return null; } - pkg.bases = (pkg.bases || []).concat( - pkg.resources || [], - pkg.components || [] - ); - pkg.images = pkg.images || []; - return pkg; } @@ -141,8 +151,30 @@ export function extractPackageFile(content: string): PackageFile | null { } // grab the remote bases - for (const base of pkg.bases) { - const dep = extractBase(base); + for (const base of pkg.bases ?? []) { + const dep = extractResource(base); + if (dep) { + deps.push({ + ...dep, + depType: pkg.kind, + }); + } + } + + // grab the remote resources + for (const resource of pkg.resources ?? []) { + const dep = extractResource(resource); + if (dep) { + deps.push({ + ...dep, + depType: pkg.kind, + }); + } + } + + // grab the remote components + for (const component of pkg.components ?? []) { + const dep = extractResource(component); if (dep) { deps.push({ ...dep, @@ -152,7 +184,7 @@ export function extractPackageFile(content: string): PackageFile | null { } // grab the image tags - for (const image of pkg.images) { + for (const image of pkg.images ?? []) { const dep = extractImage(image); if (dep) { deps.push({ @@ -162,6 +194,17 @@ export function extractPackageFile(content: string): PackageFile | null { } } + // grab the helm charts + for (const helmChart of pkg.helmCharts ?? []) { + const dep = extractHelmChart(helmChart); + if (dep) { + deps.push({ + ...dep, + depType: 'HelmChart', + }); + } + } + if (!deps.length) { return null; } diff --git a/lib/manager/kustomize/readme.md b/lib/manager/kustomize/readme.md index 7defd474b62ab9..ae135b4628f38b 100644 --- a/lib/manager/kustomize/readme.md +++ b/lib/manager/kustomize/readme.md @@ -1,20 +1,23 @@ -This package will manage two parts of the `kustomization.yaml` file: +This package will manage the following parts of the `kustomization.yaml` file: -1. [remote bases](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md) +1. [remote resources](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md) 2. [image tags](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/image.md) 3. [components](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/components.md) +4. [helm charts](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/chart.md) +5. [remote bases](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md) (deprecated since kustomize v2.1.0) **How It Works** 1. Renovate will search each repository for any `kustomization.yaml` files. -2. Existing dependencies will be extracted from remote bases & image tags +2. Existing dependencies will be extracted from remote bases, image tags & helm charts 3. Renovate will resolve the dependency's source repository and check for SemVer tags if found. 4. If an update was found, Renovate will update `kustomization.yaml` -This manager uses two `depType`s to allow a fine-grained control of which dependencies are upgraded: +This manager uses three `depType`s to allow a fine-grained control of which dependencies are upgraded: - Component - Kustomization +- HelmChart **Limitations** diff --git a/lib/manager/kustomize/types.ts b/lib/manager/kustomize/types.ts index 8693cd568ebcb2..22e0ef573850e4 100644 --- a/lib/manager/kustomize/types.ts +++ b/lib/manager/kustomize/types.ts @@ -4,8 +4,18 @@ export interface Image { newName?: string; digest?: string; } + +export interface HelmChart { + name: string; + repo: string; + version: string; +} + export interface Kustomize { kind: string; - bases: string[]; - images: Image[]; + bases?: string[]; // deprecated since kustomize v2.1.0 + resources?: string[]; + components?: string[]; + images?: Image[]; + helmCharts?: HelmChart[]; } diff --git a/lib/manager/leiningen/extract.spec.ts b/lib/manager/leiningen/extract.spec.ts index b5c250ebc67fdc..368a459ae3de55 100644 --- a/lib/manager/leiningen/extract.spec.ts +++ b/lib/manager/leiningen/extract.spec.ts @@ -13,9 +13,7 @@ describe('manager/leiningen/extract', () => { it('trimAtKey', () => { expect(trimAtKey('foo', 'bar')).toBeNull(); expect(trimAtKey(':dependencies ', 'dependencies')).toBeNull(); - expect(trimAtKey(':dependencies \nfoobar', 'dependencies')).toEqual( - 'foobar' - ); + expect(trimAtKey(':dependencies \nfoobar', 'dependencies')).toBe('foobar'); }); it('extractFromVectors', () => { expect(extractFromVectors('')).toBeEmptyArray(); diff --git a/lib/manager/leiningen/extract.ts b/lib/manager/leiningen/extract.ts index 1986c36e743cc8..9d83ff1022c7ee 100644 --- a/lib/manager/leiningen/extract.ts +++ b/lib/manager/leiningen/extract.ts @@ -4,7 +4,7 @@ import type { PackageDependency, PackageFile } from '../types'; import type { ExtractContext, ExtractedVariables } from './types'; export function trimAtKey(str: string, kwName: string): string | null { - const regex = new RegExp(`:${kwName}(?=\\s)`); // TODO #12070 + const regex = new RegExp(`:${kwName}(?=\\s)`); // TODO #12872 lookahead const keyOffset = str.search(regex); if (keyOffset < 0) { return null; @@ -105,7 +105,7 @@ function extractLeinRepos(content: string): string[] { const result = []; const repoContent = trimAtKey( - content.replace(/;;.*(?=[\r\n])/g, ''), // get rid of comments // TODO #12070 + content.replace(/;;.*(?=[\r\n])/g, ''), // get rid of comments // TODO #12872 lookahead 'repositories' ); @@ -125,10 +125,11 @@ function extractLeinRepos(content: string): string[] { } } const repoSectionContent = repoContent.slice(0, endIdx); - const matches = repoSectionContent.match(/"https?:\/\/[^"]*"/g) || []; // TODO #12070 + const matches = + repoSectionContent.match(regEx(/"https?:\/\/[^"]*"/g)) || []; const urls = matches.map((x) => x.replace(regEx(/^"/), '').replace(regEx(/"$/), '') - ); // TODO #12071 + ); urls.forEach((url) => result.push(url)); } diff --git a/lib/manager/maven/__fixtures__/simple.pom.xml b/lib/manager/maven/__fixtures__/simple.pom.xml index 138cc1e7b3e61f..0ec10b5eebdeb7 100644 --- a/lib/manager/maven/__fixtures__/simple.pom.xml +++ b/lib/manager/maven/__fixtures__/simple.pom.xml @@ -42,9 +42,15 @@ 0.0.1 - org.example - bar - 1.0.0 + + org.example + + + bar + + + 1.0.0 + diff --git a/lib/manager/maven/__snapshots__/extract.spec.ts.snap b/lib/manager/maven/__snapshots__/extract.spec.ts.snap index 424b9319f1528b..133e526a063892 100644 --- a/lib/manager/maven/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/maven/__snapshots__/extract.spec.ts.snap @@ -28,7 +28,7 @@ Object { "currentValue": "1.0.0", "datasource": "maven", "depName": "org.example:bar", - "fileReplacePosition": 1053, + "fileReplacePosition": 1093, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -38,7 +38,7 @@ Object { "currentValue": "2.4.2", "datasource": "maven", "depName": "org.apache.maven.plugins:maven-release-plugin", - "fileReplacePosition": 1287, + "fileReplacePosition": 1347, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -48,7 +48,7 @@ Object { "currentValue": "1.8.1", "datasource": "maven", "depName": "org.apache.maven.scm:maven-scm-provider-gitexe", - "fileReplacePosition": 1485, + "fileReplacePosition": 1545, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -58,7 +58,7 @@ Object { "currentValue": "0.0.1", "datasource": "maven", "depName": "org.example:\${artifact-id-placeholder}", - "fileReplacePosition": 2230, + "fileReplacePosition": 2290, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -68,7 +68,7 @@ Object { "currentValue": "0.0.1", "datasource": "maven", "depName": "\${group-id-placeholder}:baz", - "fileReplacePosition": 2380, + "fileReplacePosition": 2440, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -78,7 +78,7 @@ Object { "currentValue": "\${quuxVersion}", "datasource": "maven", "depName": "\${quuxGroup}:\${quuxId}", - "fileReplacePosition": 2525, + "fileReplacePosition": 2585, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -88,7 +88,7 @@ Object { "currentValue": "\${quuxVersion}", "datasource": "maven", "depName": "\${quuxGroup}:\${quuxId}-test", - "fileReplacePosition": 2684, + "fileReplacePosition": 2744, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -99,7 +99,7 @@ Object { "datasource": "maven", "depName": "org.example:quuz", "depType": "test", - "fileReplacePosition": 2832, + "fileReplacePosition": 2892, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -109,7 +109,7 @@ Object { "currentValue": "it's not a version", "datasource": "maven", "depName": "org.example:quuuz", - "fileReplacePosition": 2998, + "fileReplacePosition": 3058, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -119,7 +119,7 @@ Object { "currentValue": "[1.0.0]", "datasource": "maven", "depName": "org.example:hard-range", - "fileReplacePosition": 3156, + "fileReplacePosition": 3216, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -129,7 +129,7 @@ Object { "currentValue": "\${profile-placeholder}", "datasource": "maven", "depName": "org.example:profile-artifact", - "fileReplacePosition": 3418, + "fileReplacePosition": 3478, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -139,7 +139,7 @@ Object { "currentValue": "2.17", "datasource": "maven", "depName": "org.apache.maven.plugins:maven-checkstyle-plugin", - "fileReplacePosition": 3694, + "fileReplacePosition": 3754, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", diff --git a/lib/manager/maven/__snapshots__/index.spec.ts.snap b/lib/manager/maven/__snapshots__/index.spec.ts.snap index a547c337869389..91cb4a394abbaa 100644 --- a/lib/manager/maven/__snapshots__/index.spec.ts.snap +++ b/lib/manager/maven/__snapshots__/index.spec.ts.snap @@ -29,7 +29,7 @@ Array [ "currentValue": "1.0.0", "datasource": "maven", "depName": "org.example:bar", - "fileReplacePosition": 1053, + "fileReplacePosition": 1093, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -39,7 +39,7 @@ Array [ "currentValue": "2.4.2", "datasource": "maven", "depName": "org.apache.maven.plugins:maven-release-plugin", - "fileReplacePosition": 1287, + "fileReplacePosition": 1347, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -49,7 +49,7 @@ Array [ "currentValue": "1.8.1", "datasource": "maven", "depName": "org.apache.maven.scm:maven-scm-provider-gitexe", - "fileReplacePosition": 1485, + "fileReplacePosition": 1545, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -59,7 +59,7 @@ Array [ "currentValue": "0.0.1", "datasource": "maven", "depName": "org.example:\${artifact-id-placeholder}", - "fileReplacePosition": 2230, + "fileReplacePosition": 2290, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -70,7 +70,7 @@ Array [ "currentValue": "0.0.1", "datasource": "maven", "depName": "\${group-id-placeholder}:baz", - "fileReplacePosition": 2380, + "fileReplacePosition": 2440, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -104,7 +104,7 @@ Array [ "datasource": "maven", "depName": "org.example:quuz", "depType": "test", - "fileReplacePosition": 2832, + "fileReplacePosition": 2892, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -114,7 +114,7 @@ Array [ "currentValue": "it's not a version", "datasource": "maven", "depName": "org.example:quuuz", - "fileReplacePosition": 2998, + "fileReplacePosition": 3058, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -124,7 +124,7 @@ Array [ "currentValue": "[1.0.0]", "datasource": "maven", "depName": "org.example:hard-range", - "fileReplacePosition": 3156, + "fileReplacePosition": 3216, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -134,7 +134,7 @@ Array [ "currentValue": "\${profile-placeholder}", "datasource": "maven", "depName": "org.example:profile-artifact", - "fileReplacePosition": 3418, + "fileReplacePosition": 3478, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", @@ -145,7 +145,7 @@ Array [ "currentValue": "2.17", "datasource": "maven", "depName": "org.apache.maven.plugins:maven-checkstyle-plugin", - "fileReplacePosition": 3694, + "fileReplacePosition": 3754, "registryUrls": Array [ "https://repo.maven.apache.org/maven2", "https://maven.atlassian.com/content/repositories/atlassian-public/", diff --git a/lib/manager/maven/extract.spec.ts b/lib/manager/maven/extract.spec.ts index 84961fc53878a1..e850cd1808c6b2 100644 --- a/lib/manager/maven/extract.spec.ts +++ b/lib/manager/maven/extract.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-template-curly-in-string */ import { loadFixture } from '../../../test/util'; import { extractPackage } from './extract'; diff --git a/lib/manager/maven/extract.ts b/lib/manager/maven/extract.ts index 28e72a80540a4e..e08aebc8f7b1a6 100644 --- a/lib/manager/maven/extract.ts +++ b/lib/manager/maven/extract.ts @@ -41,9 +41,9 @@ function depFromNode(node: XmlElement): PackageDependency | null { if (!('valueWithPath' in node)) { return null; } - let groupId = node.valueWithPath('groupId'); - const artifactId = node.valueWithPath('artifactId'); - const currentValue = node.valueWithPath('version'); + let groupId = node.valueWithPath('groupId')?.trim(); + const artifactId = node.valueWithPath('artifactId')?.trim(); + const currentValue = node.valueWithPath('version')?.trim(); if (!groupId && node.name === 'plugin') { groupId = 'org.apache.maven.plugins'; @@ -63,7 +63,7 @@ function depFromNode(node: XmlElement): PackageDependency | null { registryUrls, }; - const depType = node.valueWithPath('scope'); + const depType = node.valueWithPath('scope')?.trim(); if (depType) { result.depType = depType; } @@ -202,7 +202,7 @@ export function extractPackage( if (repositories?.children) { const repoUrls = []; for (const repo of repositories.childrenNamed('repository')) { - const repoUrl = repo.valueWithPath('url'); + const repoUrl = repo.valueWithPath('url')?.trim(); if (repoUrl) { repoUrls.push(repoUrl); } @@ -216,7 +216,7 @@ export function extractPackage( if (packageFile && project.childNamed('parent')) { const parentPath = - project.valueWithPath('parent.relativePath') || '../pom.xml'; + project.valueWithPath('parent.relativePath')?.trim() || '../pom.xml'; result.parent = resolveParentFile(packageFile, parentPath); } @@ -273,7 +273,7 @@ export function resolveParents(packages: PackageFile[]): PackageFile[] { const pkg = extractedPackages[name]; pkg.deps.forEach((rawDep) => { const urlsSet = new Set([...rawDep.registryUrls, ...registryUrls[name]]); - rawDep.registryUrls = [...urlsSet]; // eslint-disable-line no-param-reassign + rawDep.registryUrls = [...urlsSet]; }); }); @@ -297,9 +297,9 @@ function cleanResult( packageFiles: PackageFile>[] ): PackageFile>[] { packageFiles.forEach((packageFile) => { - delete packageFile.mavenProps; // eslint-disable-line no-param-reassign + delete packageFile.mavenProps; packageFile.deps.forEach((dep) => { - delete dep.propSource; // eslint-disable-line no-param-reassign + delete dep.propSource; }); }); return packageFiles; diff --git a/lib/manager/maven/index.spec.ts b/lib/manager/maven/index.spec.ts index fffedd425eb149..3202922bac9595 100644 --- a/lib/manager/maven/index.spec.ts +++ b/lib/manager/maven/index.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-template-curly-in-string */ import { fs, loadFixture } from '../../../test/util'; import type { PackageDependency, PackageFile } from '../types'; import { extractPackage, resolveParents } from './extract'; diff --git a/lib/manager/mix/artifacts.spec.ts b/lib/manager/mix/artifacts.spec.ts index 8a43a5e7de43e9..fb51593334454b 100644 --- a/lib/manager/mix/artifacts.spec.ts +++ b/lib/manager/mix/artifacts.spec.ts @@ -1,7 +1,7 @@ import { join } from 'upath'; import { envMock, exec, mockExecAll } from '../../../test/exec-util'; import { env, fs, hostRules } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as docker from '../../util/exec/docker'; import type { UpdateArtifactsConfig } from '../types'; @@ -25,11 +25,11 @@ describe('manager/mix/artifacts', () => { jest.resetModules(); env.getChildProcessEnv.mockReturnValue(envMock.basic); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns null if no mix.lock found', async () => { @@ -82,7 +82,7 @@ describe('manager/mix/artifacts', () => { it('returns updated mix.lock', async () => { jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce(); - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Old mix.lock'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('mix.lock'); const execSnapshots = mockExecAll(exec); @@ -100,7 +100,7 @@ describe('manager/mix/artifacts', () => { it('authenticates to private repositories', async () => { jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce(); - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Old mix.lock'); fs.findLocalSiblingOrParent.mockResolvedValueOnce('mix.lock'); const execSnapshots = mockExecAll(exec); @@ -140,7 +140,7 @@ describe('manager/mix/artifacts', () => { }); it('returns updated mix.lock in subdir', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.findLocalSiblingOrParent.mockResolvedValueOnce('subdir/mix.lock'); mockExecAll(exec); expect( diff --git a/lib/manager/mix/artifacts.ts b/lib/manager/mix/artifacts.ts index 48dc61553f44e4..b53d712d71091e 100644 --- a/lib/manager/mix/artifacts.ts +++ b/lib/manager/mix/artifacts.ts @@ -1,7 +1,8 @@ import { quote } from 'shlex'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { findLocalSiblingOrParent, readLocalFile, @@ -75,8 +76,8 @@ export async function updateArtifacts({ cwdFile: packageFileName, docker: { image: 'elixir', - preCommands, }, + preCommands, }; const command = [ 'mix', @@ -92,7 +93,7 @@ export async function updateArtifacts({ throw err; } - logger.warn( + logger.debug( { err, message: err.message, command }, 'Failed to update Mix lock file' ); diff --git a/lib/manager/mix/extract.spec.ts b/lib/manager/mix/extract.spec.ts index 009fd1a5605bdf..107ca67b6287bb 100644 --- a/lib/manager/mix/extract.spec.ts +++ b/lib/manager/mix/extract.spec.ts @@ -1,12 +1,12 @@ import { loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { extractPackageFile } from '.'; const sample = loadFixture('mix.exs'); describe('manager/mix/extract', () => { beforeEach(() => { - setGlobalConfig({ localDir: '' }); + GlobalConfig.set({ localDir: '' }); }); describe('extractPackageFile()', () => { diff --git a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap index eeeb8bede77128..1c419ebf2e122c 100644 --- a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap +++ b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap @@ -389,6 +389,7 @@ Object { "datasource": "npm", "depName": "yarn", "depType": "packageManager", + "lookupName": "@yarnpkg/cli", "prettyDepType": "packageManager", }, ], diff --git a/lib/manager/npm/extract/__snapshots__/pnpm.spec.ts.snap b/lib/manager/npm/extract/__snapshots__/pnpm.spec.ts.snap index 6c4a4b65286334..2ed1382724520a 100644 --- a/lib/manager/npm/extract/__snapshots__/pnpm.spec.ts.snap +++ b/lib/manager/npm/extract/__snapshots__/pnpm.spec.ts.snap @@ -1,33 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/npm/extract/pnpm .detectPnpmWorkspaces() filters none matching packages 1`] = ` -Array [ - Object { - "packageFile": "package.json", - "pnpmShrinkwrap": "pnpm-lock.yaml", - }, - Object { - "packageFile": "nested-packages/group/a/package.json", - "packageJsonName": "@demo/nested-group-a", - "pnpmShrinkwrap": "pnpm-lock.yaml", - }, - Object { - "packageFile": "not-matching/b/package.json", - "packageJsonName": "@not-matching/b", - "pnpmShrinkwrap": undefined, - }, -] -`; - -exports[`manager/npm/extract/pnpm .detectPnpmWorkspaces() skips when pnpm shrinkwrap file has already been provided 1`] = ` -Array [ - Object { - "packageFile": "package.json", - "pnpmShrinkwrap": "pnpm-lock.yaml", - }, -] -`; - exports[`manager/npm/extract/pnpm .detectPnpmWorkspaces() uses pnpm workspaces 1`] = ` Array [ Object { @@ -66,11 +38,3 @@ Array [ }, ] `; - -exports[`manager/npm/extract/pnpm .extractPnpmFilters() detects errors in pnpm-workspace.yml file structure 1`] = `null`; - -exports[`manager/npm/extract/pnpm .extractPnpmFilters() detects errors when opening pnpm-workspace.yml file 1`] = `null`; - -exports[`manager/npm/extract/pnpm .findPnpmWorkspace() detects missing pnpm-lock.yaml when pnpm-workspace.yaml was already found 1`] = `null`; - -exports[`manager/npm/extract/pnpm .findPnpmWorkspace() detects missing pnpm-workspace.yaml 1`] = `null`; diff --git a/lib/manager/npm/extract/index.spec.ts b/lib/manager/npm/extract/index.spec.ts index d12c23e157bed9..c9c74ae51d4cf6 100644 --- a/lib/manager/npm/extract/index.spec.ts +++ b/lib/manager/npm/extract/index.spec.ts @@ -153,7 +153,6 @@ describe('manager/npm/extract/index', () => { it('finds and filters .npmrc with variables', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === '.npmrc') { - // eslint-disable-next-line return 'registry=https://registry.npmjs.org\n//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}\n'; } return null; @@ -163,7 +162,7 @@ describe('manager/npm/extract/index', () => { 'package.json', {} ); - expect(res.npmrc).toEqual('registry=https://registry.npmjs.org\n'); + expect(res.npmrc).toBe('registry=https://registry.npmjs.org\n'); }); it('finds lerna', async () => { fs.readLocalFile = jest.fn((fileName) => { diff --git a/lib/manager/npm/extract/index.ts b/lib/manager/npm/extract/index.ts index 1702c419c76809..6504c3e49f9aa4 100644 --- a/lib/manager/npm/extract/index.ts +++ b/lib/manager/npm/extract/index.ts @@ -1,6 +1,6 @@ import is from '@sindresorhus/is'; import validateNpmPackageName from 'validate-npm-package-name'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { CONFIG_VALIDATION } from '../../../constants/error-messages'; import * as datasourceGithubTags from '../../../datasource/github-tags'; import { id as npmId } from '../../../datasource/npm'; @@ -27,12 +27,13 @@ function parseDepName(depType: string, key: string): string { return key; } - const [, depName] = /((?:@[^/]+\/)?[^/@]+)$/.exec(key) ?? []; // TODO #12070 + const [, depName] = regEx(/((?:@[^/]+\/)?[^/@]+)$/).exec(key) ?? []; return depName; } -const RE_REPOSITORY_GITHUB_SSH_FORMAT = - /(?:git@)github.com:([^/]+)\/([^/.]+)(?:\.git)?/; // TODO #12070 +const RE_REPOSITORY_GITHUB_SSH_FORMAT = regEx( + /(?:git@)github.com:([^/]+)\/([^/.]+)(?:\.git)?/ +); export async function extractPackageFile( content: string, @@ -49,7 +50,7 @@ export async function extractPackageFile( logger.debug({ fileName }, 'Invalid JSON'); return null; } - // eslint-disable-next-line no-underscore-dangle + if (packageJson._id && packageJson._args && packageJson._from) { logger.debug('Ignoring vendorised package.json'); return null; @@ -107,13 +108,16 @@ export async function extractPackageFile( } else { npmrc = config.npmrc || ''; if (npmrc.length) { - npmrc = npmrc.replace(/\n?$/, '\n'); // TODO #12070 + npmrc = npmrc.replace(/\n?$/, '\n'); // TODO #12875 add \n to front when used with re2 } if (repoNpmrc?.includes('package-lock')) { logger.debug('Stripping package-lock setting from .npmrc'); - repoNpmrc = repoNpmrc.replace(/(^|\n)package-lock.*?(\n|$)/g, '\n'); // TODO #12070 + repoNpmrc = repoNpmrc.replace( + regEx(/(^|\n)package-lock.*?(\n|$)/g), + '\n' + ); } - if (repoNpmrc.includes('=${') && !getGlobalConfig().exposeAllEnv) { + if (repoNpmrc.includes('=${') && !GlobalConfig.get('exposeAllEnv')) { logger.debug( { npmrcFileName }, 'Stripping .npmrc file of lines with variables' @@ -191,6 +195,12 @@ export async function extractPackageFile( dep.datasource = npmId; dep.commitMessageTopic = 'Yarn'; constraints.yarn = dep.currentValue; + if ( + dep.currentValue.startsWith('2') || + dep.currentValue.startsWith('3') + ) { + dep.lookupName = '@yarnpkg/cli'; + } } else if (depName === 'npm') { dep.datasource = npmId; dep.commitMessageTopic = 'npm'; @@ -274,10 +284,10 @@ export async function extractPackageFile( const matchUrlSshFormat = RE_REPOSITORY_GITHUB_SSH_FORMAT.exec(depNamePart); if (matchUrlSshFormat === null) { githubOwnerRepo = depNamePart - .replace(/^github:/, '') // TODO #12070 - .replace(/^git\+/, '') // TODO #12070 - .replace(/^https:\/\/github\.com\//, '') // TODO #12070 - .replace(/\.git$/, ''); // TODO #12070 + .replace(regEx(/^github:/), '') + .replace(regEx(/^git\+/), '') + .replace(regEx(/^https:\/\/github\.com\//), '') + .replace(regEx(/\.git$/), ''); const githubRepoSplit = githubOwnerRepo.split('/'); if (githubRepoSplit.length !== 2) { dep.skipReason = SkipReason.UnknownVersion; @@ -289,7 +299,7 @@ export async function extractPackageFile( githubRepo = matchUrlSshFormat[2]; githubOwnerRepo = `${githubOwner}/${githubRepo}`; } - const githubValidRegex = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/; // TODO #12070 + const githubValidRegex = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/; // TODO #12872 lookahead if ( !githubValidRegex.test(githubOwner) || !githubValidRegex.test(githubRepo) @@ -304,8 +314,8 @@ export async function extractPackageFile( dep.lookupName = githubOwnerRepo; dep.pinDigests = false; } else if ( - /^[0-9a-f]{7}$/.test(depRefPart) || // TODO #12070 - /^[0-9a-f]{40}$/.test(depRefPart) // TODO #12070 + regEx(/^[0-9a-f]{7}$/).test(depRefPart) || + regEx(/^[0-9a-f]{40}$/).test(depRefPart) ) { dep.currentRawValue = dep.currentValue; dep.currentValue = null; diff --git a/lib/manager/npm/extract/monorepo.spec.ts b/lib/manager/npm/extract/monorepo.spec.ts index 19763b49b334ae..128563c7310337 100644 --- a/lib/manager/npm/extract/monorepo.spec.ts +++ b/lib/manager/npm/extract/monorepo.spec.ts @@ -50,7 +50,7 @@ describe('manager/npm/extract/monorepo', () => { ] as any; await detectMonorepos(packageFiles, false); expect(packageFiles).toMatchSnapshot(); - expect(packageFiles[1].managerData.lernaJsonFile).toEqual('lerna.json'); + expect(packageFiles[1].managerData.lernaJsonFile).toBe('lerna.json'); expect( packageFiles.some((packageFile) => packageFile.deps?.some((dep) => dep.skipReason) @@ -104,7 +104,7 @@ describe('manager/npm/extract/monorepo', () => { ] as any; await detectMonorepos(packageFiles, true); expect(packageFiles).toMatchSnapshot(); - expect(packageFiles[1].managerData.lernaJsonFile).toEqual('lerna.json'); + expect(packageFiles[1].managerData.lernaJsonFile).toBe('lerna.json'); expect( packageFiles.some((packageFile) => packageFile.deps?.some((dep) => dep.skipReason) @@ -134,7 +134,7 @@ describe('manager/npm/extract/monorepo', () => { ]; await detectMonorepos(packageFiles, false); expect(packageFiles).toMatchSnapshot(); - expect(packageFiles[1].managerData.lernaJsonFile).toEqual('lerna.json'); + expect(packageFiles[1].managerData.lernaJsonFile).toBe('lerna.json'); }); it('uses yarn workspaces package settings without lerna', async () => { diff --git a/lib/manager/npm/extract/monorepo.ts b/lib/manager/npm/extract/monorepo.ts index f8128902e7fb63..a33a21f8bb5272 100644 --- a/lib/manager/npm/extract/monorepo.ts +++ b/lib/manager/npm/extract/monorepo.ts @@ -42,7 +42,7 @@ export async function detectMonorepos( if (!updateInternalDeps) { p.deps?.forEach((dep) => { if (internalPackageNames.includes(dep.depName)) { - dep.skipReason = SkipReason.InternalPackage; // eslint-disable-line no-param-reassign + dep.skipReason = SkipReason.InternalPackage; } }); } @@ -61,7 +61,7 @@ export async function detectMonorepos( if (!updateInternalDeps) { subPackage.deps?.forEach((dep) => { if (internalPackageNames.includes(dep.depName)) { - dep.skipReason = SkipReason.InternalPackage; // eslint-disable-line no-param-reassign + dep.skipReason = SkipReason.InternalPackage; } }); } diff --git a/lib/manager/npm/extract/npm.spec.ts b/lib/manager/npm/extract/npm.spec.ts index 8e704676f4bd40..1cd429d659f3b9 100644 --- a/lib/manager/npm/extract/npm.spec.ts +++ b/lib/manager/npm/extract/npm.spec.ts @@ -23,7 +23,7 @@ describe('manager/npm/extract/npm', () => { const res = await getNpmLock('package.json'); expect(res).toMatchSnapshot(); expect(Object.keys(res.lockedVersions)).toHaveLength(7); - expect(res.lockfileVersion).toEqual(2); + expect(res.lockfileVersion).toBe(2); }); it('returns empty if no deps', async () => { fs.readLocalFile.mockResolvedValueOnce('{}'); diff --git a/lib/manager/npm/extract/pnpm.spec.ts b/lib/manager/npm/extract/pnpm.spec.ts index d27216433f47ad..650ef274016c24 100644 --- a/lib/manager/npm/extract/pnpm.spec.ts +++ b/lib/manager/npm/extract/pnpm.spec.ts @@ -1,6 +1,6 @@ import yaml from 'js-yaml'; import { getFixturePath, logger } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import * as fs from '../../../util/fs'; import { detectPnpmWorkspaces, @@ -10,7 +10,7 @@ import { describe('manager/npm/extract/pnpm', () => { beforeAll(() => { - setGlobalConfig({ localDir: getFixturePath('pnpm-monorepo/', '..') }); + GlobalConfig.set({ localDir: getFixturePath('pnpm-monorepo/', '..') }); }); describe('.extractPnpmFilters()', () => { @@ -24,8 +24,7 @@ describe('manager/npm/extract/pnpm', () => { '..' ); const res = await extractPnpmFilters(workSpaceFilePath); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toBeNull(); expect(logger.logger.trace).toHaveBeenCalledWith( { fileName: expect.any(String), @@ -40,8 +39,7 @@ describe('manager/npm/extract/pnpm', () => { }); const res = await extractPnpmFilters('pnpm-workspace.yml'); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toBeNull(); expect(logger.logger.trace).toHaveBeenCalledWith( expect.objectContaining({ fileName: expect.any(String), @@ -58,8 +56,7 @@ describe('manager/npm/extract/pnpm', () => { const packageFile = 'package.json'; const res = await findPnpmWorkspace(packageFile); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toBeNull(); expect(logger.logger.trace).toHaveBeenCalledWith( expect.objectContaining({ packageFile }), 'Failed to locate pnpm-workspace.yaml in a parent directory.' @@ -71,8 +68,7 @@ describe('manager/npm/extract/pnpm', () => { const packageFile = 'package.json'; const res = await findPnpmWorkspace(packageFile); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toBeNull(); expect(logger.logger.trace).toHaveBeenCalledWith( expect.objectContaining({ workspaceYamlPath: 'pnpm-workspace.yaml', @@ -138,8 +134,12 @@ describe('manager/npm/extract/pnpm', () => { ]; await detectPnpmWorkspaces(packageFiles); - // FIXME: explicit assert condition - expect(packageFiles).toMatchSnapshot(); + expect(packageFiles).toEqual([ + { + packageFile: 'package.json', + pnpmShrinkwrap: 'pnpm-lock.yaml', + }, + ]); }); it('filters none matching packages', async () => { @@ -161,14 +161,28 @@ describe('manager/npm/extract/pnpm', () => { ]; await detectPnpmWorkspaces(packageFiles); - // FIXME: explicit assert condition - expect(packageFiles).toMatchSnapshot(); + expect(packageFiles).toEqual([ + { + packageFile: 'package.json', + pnpmShrinkwrap: 'pnpm-lock.yaml', + }, + { + packageFile: 'nested-packages/group/a/package.json', + packageJsonName: '@demo/nested-group-a', + pnpmShrinkwrap: 'pnpm-lock.yaml', + }, + { + packageFile: 'not-matching/b/package.json', + packageJsonName: '@not-matching/b', + pnpmShrinkwrap: undefined, + }, + ]); expect( packageFiles.find( (packageFile) => packageFile.packageFile === 'not-matching/b/package.json' ).pnpmShrinkwrap - ).not.toBeDefined(); + ).toBeUndefined(); }); }); }); diff --git a/lib/manager/npm/extract/pnpm.ts b/lib/manager/npm/extract/pnpm.ts index a394c6aafe09b1..a19666f83bf387 100644 --- a/lib/manager/npm/extract/pnpm.ts +++ b/lib/manager/npm/extract/pnpm.ts @@ -85,13 +85,13 @@ export async function detectPnpmWorkspaces( { packageFile, pnpmShrinkwrap }, 'Found an existing pnpm shrinkwrap file; skipping pnpm monorepo check.' ); - continue; // eslint-disable-line no-continue + continue; } // search for corresponding pnpm workspace const pnpmWorkspace = await findPnpmWorkspace(packageFile); if (pnpmWorkspace === null) { - continue; // eslint-disable-line no-continue + continue; } const { workspaceYamlPath, lockFilePath } = pnpmWorkspace; @@ -105,7 +105,7 @@ export async function detectPnpmWorkspaces( packageFilters !== null && matchesAnyPattern( packageFile, - packageFilters.map((filter) => filter.replace(/\/?$/, '/package.json')) // TODO #12070 #12071 + packageFilters.map((filter) => filter.replace(/\/?$/, '/package.json')) // TODO #12875 ); if (isPackageInWorkspace) { p.pnpmShrinkwrap = lockFilePath; diff --git a/lib/manager/npm/extract/yarn.spec.ts b/lib/manager/npm/extract/yarn.spec.ts index 94ae015d7baf55..6cc368a48cd067 100644 --- a/lib/manager/npm/extract/yarn.spec.ts +++ b/lib/manager/npm/extract/yarn.spec.ts @@ -27,7 +27,7 @@ describe('manager/npm/extract/yarn', () => { fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock); const res = await getYarnLock('package.json'); expect(res.isYarn1).toBeFalse(); - expect(res.lockfileVersion).toBe(NaN); + expect(res.lockfileVersion).toBeNaN(); expect(res.lockedVersions).toMatchSnapshot(); expect(Object.keys(res.lockedVersions)).toHaveLength(8); }); diff --git a/lib/manager/npm/post-update/__snapshots__/npm.spec.ts.snap b/lib/manager/npm/post-update/__snapshots__/npm.spec.ts.snap index b21eac557d17a8..0abab7674b2c19 100644 --- a/lib/manager/npm/post-update/__snapshots__/npm.spec.ts.snap +++ b/lib/manager/npm/post-update/__snapshots__/npm.spec.ts.snap @@ -45,29 +45,6 @@ Array [ ] `; -exports[`manager/npm/post-update/npm massages lock files 1`] = ` -Array [ - Object { - "cmd": "npm install --package-lock-only --no-audit --ignore-scripts", - "options": Object { - "cwd": "some-dir", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - exports[`manager/npm/post-update/npm performs full install 1`] = `Array []`; exports[`manager/npm/post-update/npm performs lock file maintenance 1`] = ` diff --git a/lib/manager/npm/post-update/index.ts b/lib/manager/npm/post-update/index.ts index dc83a1ca949551..03ec150b52bcca 100644 --- a/lib/manager/npm/post-update/index.ts +++ b/lib/manager/npm/post-update/index.ts @@ -1,8 +1,9 @@ import is from '@sindresorhus/is'; import deepmerge from 'deepmerge'; +import detectIndent from 'detect-indent'; import { dump, load } from 'js-yaml'; import upath from 'upath'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { SYSTEM_INSUFFICIENT_DISK_SPACE } from '../../../constants/error-messages'; import { id as npmId } from '../../../datasource/npm'; import { logger } from '../../../logger'; @@ -64,7 +65,7 @@ export function determineLockFileDirs( npmLockDirs.push(upgrade.npmLock); pnpmShrinkwrapDirs.push(upgrade.pnpmShrinkwrap); } - continue; // eslint-disable-line no-continue + continue; } if (upgrade.isLockfileUpdate) { yarnLockDirs.push(upgrade.yarnLock); @@ -140,7 +141,7 @@ export async function writeExistingFiles( { packageFiles: npmFiles.map((n) => n.packageFile) }, 'Writing package.json files' ); - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); for (const packageFile of npmFiles) { const basedir = upath.join( localDir, @@ -166,36 +167,67 @@ export async function writeExistingFiles( await remove(npmLockPath); } else { logger.debug(`Writing ${npmLock}`); - let existingNpmLock = await getFile(npmLock); - const widens = []; - for (const upgrade of config.upgrades) { - if ( - upgrade.rangeStrategy === 'widen' && - upgrade.npmLock === npmLock - ) { - widens.push(upgrade.depName); - } + let existingNpmLock: string; + let detectedIndent: string; + let npmLockParsed: any; + try { + existingNpmLock = await getFile(npmLock); + detectedIndent = detectIndent(existingNpmLock).indent || ' '; + npmLockParsed = JSON.parse(existingNpmLock); + } catch (err) { + logger.warn({ err }, 'Error parsing npm lock file'); } - if (widens.length) { - logger.debug( - `Removing ${String(widens)} from ${npmLock} to force an update` - ); - try { - const npmLockParsed = JSON.parse(existingNpmLock); - if (npmLockParsed.dependencies) { - widens.forEach((depName) => { - delete npmLockParsed.dependencies[depName]; - }); + if (npmLockParsed) { + const packageNames = Object.keys(npmLockParsed?.packages || {}); // lockfileVersion=2 + const widens = []; + let lockFileChanged = false; + for (const upgrade of config.upgrades) { + if ( + upgrade.rangeStrategy === 'widen' && + upgrade.npmLock === npmLock + ) { + widens.push(upgrade.depName); } - existingNpmLock = JSON.stringify(npmLockParsed, null, 2); - } catch (err) { - logger.warn( - { npmLock }, - 'Error massaging package-lock.json for widen' + const { depName } = upgrade; + for (const packageName of packageNames) { + if ( + packageName === `node_modules/${depName}` || + packageName.startsWith(`node_modules/${depName}/`) + ) { + logger.trace({ packageName }, 'Massaging out package name'); + lockFileChanged = true; + delete npmLockParsed.packages[packageName]; + } + } + } + if (widens.length) { + logger.debug( + `Removing ${String(widens)} from ${npmLock} to force an update` + ); + lockFileChanged = true; + try { + if (npmLockParsed.dependencies) { + widens.forEach((depName) => { + delete npmLockParsed.dependencies[depName]; + }); + } + } catch (err) { + logger.warn( + { npmLock }, + 'Error massaging package-lock.json for widen' + ); + } + } + if (lockFileChanged) { + logger.debug('Massaging npm lock file before writing to disk'); + existingNpmLock = JSON.stringify( + npmLockParsed, + null, + detectedIndent ); } + await outputFile(npmLockPath, existingNpmLock); } - await outputFile(npmLockPath, existingNpmLock); } } const { yarnLock } = packageFile; @@ -219,7 +251,7 @@ export async function writeUpdatedPackageFiles( logger.debug('No files found'); return; } - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); for (const packageFile of config.updatedPackageFiles) { if (packageFile.name.endsWith('package-lock.json')) { logger.debug(`Writing package-lock file: ${packageFile.name}`); @@ -227,12 +259,14 @@ export async function writeUpdatedPackageFiles( upath.join(localDir, packageFile.name), packageFile.contents ); - continue; // eslint-disable-line + continue; } if (!packageFile.name.endsWith('package.json')) { - continue; // eslint-disable-line + continue; } logger.debug(`Writing ${String(packageFile.name)}`); + const detectedIndent = + detectIndent(packageFile.contents.toString()).indent || ' '; const massagedFile = JSON.parse(packageFile.contents.toString()); try { const { token } = hostRules.find({ @@ -254,7 +288,7 @@ export async function writeUpdatedPackageFiles( } await outputFile( upath.join(localDir, packageFile.name), - JSON.stringify(massagedFile) + JSON.stringify(massagedFile, null, detectedIndent) ); } } @@ -476,7 +510,7 @@ export async function getAdditionalFiles( } catch (err) { logger.warn({ err }, 'Error getting token for packageFile'); } - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); for (const npmLock of dirs.npmLockDirs) { const lockFileDir = upath.dirname(npmLock); const fullLockFileDir = upath.join(localDir, lockFileDir); diff --git a/lib/manager/npm/post-update/lerna.spec.ts b/lib/manager/npm/post-update/lerna.spec.ts index 511153de43b04d..d48fa30fd5a84c 100644 --- a/lib/manager/npm/post-update/lerna.spec.ts +++ b/lib/manager/npm/post-update/lerna.spec.ts @@ -1,7 +1,7 @@ import { exec as _exec } from 'child_process'; import { envMock, mockExecAll } from '../../../../test/exec-util'; import { mocked } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import * as _env from '../../../util/exec/env'; import * as _lernaHelper from './lerna'; @@ -95,7 +95,7 @@ describe('manager/npm/post-update/lerna', () => { }); it('allows scripts for trust level high', async () => { const execSnapshots = mockExecAll(exec); - setGlobalConfig({ allowScripts: true }); + GlobalConfig.set({ allowScripts: true }); const res = await lernaHelper.generateLockFiles( lernaPkgFile('npm'), 'some-dir', diff --git a/lib/manager/npm/post-update/lerna.ts b/lib/manager/npm/post-update/lerna.ts index a7b3329cce141b..75cbd26cd20cf1 100644 --- a/lib/manager/npm/post-update/lerna.ts +++ b/lib/manager/npm/post-update/lerna.ts @@ -1,9 +1,10 @@ import semver, { validRange } from 'semver'; import { quote } from 'shlex'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import { logger } from '../../../logger'; -import { ExecOptions, exec } from '../../../util/exec'; +import { exec } from '../../../util/exec'; +import type { ExecOptions } from '../../../util/exec/types'; import type { PackageFile, PostUpdateConfig } from '../../types'; import { getNodeConstraint } from './node-version'; import type { GenerateLockFileResult } from './types'; @@ -57,7 +58,7 @@ export async function generateLockFiles( if (validRange(npmCompatibility)) { installNpm += `@${quote(npmCompatibility)} || true`; } - preCommands.push(installNpm, 'hash -d npm'); + preCommands.push(installNpm, 'hash -d npm 2>/dev/null || true'); cmdOptions = '--ignore-scripts --no-audit'; if (skipInstalls !== false) { cmdOptions += ' --package-lock-only'; @@ -67,7 +68,7 @@ export async function generateLockFiles( return { error: false }; } let lernaCommand = `lerna bootstrap --no-ci --ignore-scripts -- `; - if (getGlobalConfig().allowScripts && config.ignoreScripts !== false) { + if (GlobalConfig.get('allowScripts') && config.ignoreScripts !== false) { cmdOptions = cmdOptions.replace('--ignore-scripts ', ''); lernaCommand = lernaCommand.replace('--ignore-scripts ', ''); } @@ -83,11 +84,11 @@ export async function generateLockFiles( image: 'node', tagScheme: 'node', tagConstraint, - preCommands, }, + preCommands, }; // istanbul ignore if - if (getGlobalConfig().exposeAllEnv) { + if (GlobalConfig.get('exposeAllEnv')) { execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH; execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL; } diff --git a/lib/manager/npm/post-update/node-version.spec.ts b/lib/manager/npm/post-update/node-version.spec.ts index c32f94d27116d8..f2993e14b7bf27 100644 --- a/lib/manager/npm/post-update/node-version.spec.ts +++ b/lib/manager/npm/post-update/node-version.spec.ts @@ -13,27 +13,27 @@ describe('manager/npm/post-update/node-version', () => { fs.readLocalFile.mockResolvedValueOnce(null); fs.readLocalFile.mockResolvedValueOnce(null); const res = await getNodeConstraint(config); - expect(res).toEqual('^12.16.0'); + expect(res).toBe('^12.16.0'); }); it('returns .node-version value', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce(null); fs.readLocalFile.mockResolvedValueOnce('12.16.1\n'); const res = await getNodeConstraint(config); - expect(res).toEqual('12.16.1'); + expect(res).toBe('12.16.1'); }); it('returns .nvmrc value', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce('12.16.2\n'); const res = await getNodeConstraint(config); - expect(res).toEqual('12.16.2'); + expect(res).toBe('12.16.2'); }); it('ignores unusable ranges in dotfiles', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce('latest'); fs.readLocalFile.mockResolvedValueOnce('lts'); const res = await getNodeConstraint(config); - expect(res).toEqual('^12.16.0'); + expect(res).toBe('^12.16.0'); }); it('returns no constraint', async () => { fs.readLocalFile = jest.fn(); diff --git a/lib/manager/npm/post-update/npm.spec.ts b/lib/manager/npm/post-update/npm.spec.ts index f42342037210de..e4f9341151427b 100644 --- a/lib/manager/npm/post-update/npm.spec.ts +++ b/lib/manager/npm/post-update/npm.spec.ts @@ -1,5 +1,4 @@ import { exec as _exec } from 'child_process'; -import * as fsExtra from 'fs-extra'; import upath from 'upath'; import { envMock, mockExecAll } from '../../../../test/exec-util'; @@ -38,36 +37,9 @@ describe('manager/npm/post-update/npm', () => { { skipInstalls, postUpdateOptions }, updates ); - expect(fs.readFile).toHaveBeenCalledTimes(2); - expect(res.error).toBeUndefined(); - expect(res.lockFile).toEqual('package-lock-contents'); - expect(execSnapshots).toMatchSnapshot(); - }); - it('massages lock files', async () => { - const execSnapshots = mockExecAll(exec); - const lockContents = await fsExtra.readFile( - upath.join(__dirname, '/__fixtures__/massage1/package-lock.json'), - 'utf8' - ); - fs.readFile = jest.fn(() => lockContents) as never; - const skipInstalls = true; - const updates = [ - { - depName: '@storybook/vue', - newVersion: '6.3.12', - isLockfileUpdate: false, - }, - ]; - const res = await npmHelper.generateLockFile( - 'some-dir', - {}, - 'package-lock.json', - { skipInstalls }, - updates - ); - expect(fs.readFile).toHaveBeenCalledTimes(2); - expect(fs.outputFile).toHaveBeenCalledTimes(1); + expect(fs.readFile).toHaveBeenCalledTimes(1); expect(res.error).toBeUndefined(); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('performs lock file updates', async () => { @@ -84,9 +56,9 @@ describe('manager/npm/post-update/npm', () => { { skipInstalls }, updates ); - expect(fs.readFile).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledTimes(1); expect(res.error).toBeUndefined(); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('performs npm-shrinkwrap.json updates', async () => { @@ -109,13 +81,13 @@ describe('manager/npm/post-update/npm', () => { upath.join('some-dir', 'package-lock.json'), upath.join('some-dir', 'npm-shrinkwrap.json') ); - expect(fs.readFile).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledTimes(1); expect(fs.readFile).toHaveBeenCalledWith( upath.join('some-dir', 'npm-shrinkwrap.json'), 'utf8' ); expect(res.error).toBeUndefined(); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('performs npm-shrinkwrap.json updates (no package-lock.json)', async () => { @@ -134,13 +106,13 @@ describe('manager/npm/post-update/npm', () => { upath.join('some-dir', 'package-lock.json') ); expect(fs.move).toHaveBeenCalledTimes(0); - expect(fs.readFile).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledTimes(1); expect(fs.readFile).toHaveBeenCalledWith( upath.join('some-dir', 'npm-shrinkwrap.json'), 'utf8' ); expect(res.error).toBeUndefined(); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('performs full install', async () => { @@ -154,9 +126,9 @@ describe('manager/npm/post-update/npm', () => { 'package-lock.json', { skipInstalls, binarySource } ); - expect(fs.readFile).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledTimes(1); expect(res.error).toBeUndefined(); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('runs twice if remediating', async () => { @@ -170,9 +142,9 @@ describe('manager/npm/post-update/npm', () => { { binarySource }, [{ isRemediation: true }] ); - expect(fs.readFile).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledTimes(1); expect(res.error).toBeUndefined(); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toHaveLength(2); }); it('catches errors', async () => { @@ -185,9 +157,9 @@ describe('manager/npm/post-update/npm', () => { {}, 'package-lock.json' ); - expect(fs.readFile).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledTimes(1); expect(res.error).toBeTrue(); - expect(res.lockFile).not.toBeDefined(); + expect(res.lockFile).toBeUndefined(); expect(execSnapshots).toMatchSnapshot(); }); it('finds npm globally', async () => { @@ -198,8 +170,8 @@ describe('manager/npm/post-update/npm', () => { {}, 'package-lock.json' ); - expect(fs.readFile).toHaveBeenCalledTimes(2); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(fs.readFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('uses docker npm', async () => { @@ -211,8 +183,8 @@ describe('manager/npm/post-update/npm', () => { 'package-lock.json', { binarySource: 'docker', constraints: { npm: '^6.0.0' } } ); - expect(fs.readFile).toHaveBeenCalledTimes(2); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(fs.readFile).toHaveBeenCalledTimes(1); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('performs lock file maintenance', async () => { @@ -227,7 +199,7 @@ describe('manager/npm/post-update/npm', () => { ); expect(fs.readFile).toHaveBeenCalledTimes(1); expect(fs.remove).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); }); diff --git a/lib/manager/npm/post-update/npm.ts b/lib/manager/npm/post-update/npm.ts index 9dd5414dcdd478..2c560f0d851795 100644 --- a/lib/manager/npm/post-update/npm.ts +++ b/lib/manager/npm/post-update/npm.ts @@ -1,21 +1,13 @@ -import is from '@sindresorhus/is'; -import { validRange } from 'semver'; -import { quote } from 'shlex'; import { join } from 'upath'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { SYSTEM_INSUFFICIENT_DISK_SPACE, TEMPORARY_ERROR, } from '../../../constants/error-messages'; import { logger } from '../../../logger'; -import { ExecOptions, exec } from '../../../util/exec'; -import { - move, - outputFile, - pathExists, - readFile, - remove, -} from '../../../util/fs'; +import { exec } from '../../../util/exec'; +import type { ExecOptions, ToolConstraint } from '../../../util/exec/types'; +import { move, pathExists, readFile, remove } from '../../../util/fs'; import type { PostUpdateConfig, Upgrade } from '../../types'; import { getNodeConstraint } from './node-version'; import type { GenerateLockFileResult } from './types'; @@ -32,23 +24,10 @@ export async function generateLockFile( let lockFile = null; try { - let installNpm = 'npm i -g npm'; - const npmCompatibility = config.constraints?.npm as string; - // istanbul ignore else - if (npmCompatibility) { - // istanbul ignore else - if (validRange(npmCompatibility)) { - installNpm = `npm i -g ${quote(`npm@${npmCompatibility}`)} || true`; - } else { - logger.debug( - { npmCompatibility }, - 'npm compatibility range is not valid - skipping' - ); - } - } else { - logger.debug('No npm compatibility range found - installing npm latest'); - } - const preCommands = [installNpm, 'hash -d npm']; + const npmToolConstraint: ToolConstraint = { + toolName: 'npm', + constraint: config.constraints?.npm, + }; const commands = []; let cmdOptions = ''; if (postUpdateOptions?.includes('npmDedupe') || skipInstalls === false) { @@ -59,7 +38,7 @@ export async function generateLockFile( cmdOptions += '--package-lock-only --no-audit'; } - if (!getGlobalConfig().allowScripts || config.ignoreScripts) { + if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) { cmdOptions += ' --ignore-scripts'; } @@ -70,15 +49,15 @@ export async function generateLockFile( NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE, npm_config_store: env.npm_config_store, }, + toolConstraints: [npmToolConstraint], docker: { image: 'node', tagScheme: 'node', tagConstraint, - preCommands, }, }; // istanbul ignore if - if (getGlobalConfig().exposeAllEnv) { + if (GlobalConfig.get('exposeAllEnv')) { execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH; execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL; } @@ -111,6 +90,7 @@ export async function generateLockFile( commands.push('npm dedupe'); } + // TODO: don't assume package-lock.json is in the same directory const lockFileName = join(cwd, filename); if (upgrades.find((upgrade) => upgrade.isLockFileMaintenance)) { @@ -125,36 +105,6 @@ export async function generateLockFile( 'Error removing yarn.lock for lock file maintenance' ); } - } else { - // massage lock file for npm 7+ - try { - const lockFileParsed = JSON.parse(await readFile(lockFileName, 'utf8')); - if (is.plainObject(lockFileParsed.packages)) { - const packageNames = Object.keys(lockFileParsed.packages); - let lockFileMassaged = false; - for (const { depName } of upgrades) { - for (const packageName of packageNames) { - if ( - packageName === `node_modules/${depName}` || - packageName.startsWith(`node_modules/${depName}/`) - ) { - logger.trace({ packageName }, 'Massaging out package name'); - lockFileMassaged = true; - delete lockFileParsed.packages[packageName]; - } - } - } - if (lockFileMassaged) { - logger.debug('Writing massaged package-lock.json'); - await outputFile( - lockFileName, - JSON.stringify(lockFileParsed, null, 2) - ); - } - } - } catch (err) { - logger.warn({ err }, 'Error massaging package-lock.json'); - } } // Run the commands diff --git a/lib/manager/npm/post-update/pnpm.spec.ts b/lib/manager/npm/post-update/pnpm.spec.ts index f3f1002abff218..14c46e2dcac122 100644 --- a/lib/manager/npm/post-update/pnpm.spec.ts +++ b/lib/manager/npm/post-update/pnpm.spec.ts @@ -28,7 +28,7 @@ describe('manager/npm/post-update/pnpm', () => { fs.readFile = jest.fn(() => 'package-lock-contents') as never; const res = await pnpmHelper.generateLockFile('some-dir', {}, config); expect(fs.readFile).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('catches errors', async () => { @@ -39,7 +39,7 @@ describe('manager/npm/post-update/pnpm', () => { const res = await pnpmHelper.generateLockFile('some-dir', {}, config); expect(fs.readFile).toHaveBeenCalledTimes(1); expect(res.error).toBeTrue(); - expect(res.lockFile).not.toBeDefined(); + expect(res.lockFile).toBeUndefined(); expect(execSnapshots).toMatchSnapshot(); }); it('finds pnpm globally', async () => { @@ -47,7 +47,7 @@ describe('manager/npm/post-update/pnpm', () => { fs.readFile = jest.fn(() => 'package-lock-contents') as never; const res = await pnpmHelper.generateLockFile('some-dir', {}, config); expect(fs.readFile).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); it('performs lock file maintenance', async () => { @@ -58,7 +58,7 @@ describe('manager/npm/post-update/pnpm', () => { ]); expect(fs.readFile).toHaveBeenCalledTimes(1); expect(fs.remove).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); @@ -73,7 +73,7 @@ describe('manager/npm/post-update/pnpm', () => { }, ]); expect(fs.readFile).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); // TODO: check docker preCommands }); diff --git a/lib/manager/npm/post-update/pnpm.ts b/lib/manager/npm/post-update/pnpm.ts index 7d0f62ba404e2f..93b6180d911f33 100644 --- a/lib/manager/npm/post-update/pnpm.ts +++ b/lib/manager/npm/post-update/pnpm.ts @@ -1,10 +1,11 @@ import { validRange } from 'semver'; import { quote } from 'shlex'; import { join } from 'upath'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import { logger } from '../../../logger'; -import { ExecOptions, exec } from '../../../util/exec'; +import { exec } from '../../../util/exec'; +import type { ExecOptions } from '../../../util/exec/types'; import { readFile, remove } from '../../../util/fs'; import type { PostUpdateConfig, Upgrade } from '../../types'; import { getNodeConstraint } from './node-version'; @@ -46,17 +47,17 @@ export async function generateLockFile( image: 'node', tagScheme: 'node', tagConstraint, - preCommands, }, + preCommands, }; // istanbul ignore if - if (getGlobalConfig().exposeAllEnv) { + if (GlobalConfig.get('exposeAllEnv')) { execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH; execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL; } cmd = 'pnpm'; let args = 'install --recursive --lockfile-only'; - if (!getGlobalConfig().allowScripts || config.ignoreScripts) { + if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) { args += ' --ignore-scripts'; args += ' --ignore-pnpmfile'; } diff --git a/lib/manager/npm/post-update/yarn.spec.ts b/lib/manager/npm/post-update/yarn.spec.ts index 004297a6cb9925..8c7534524da7bb 100644 --- a/lib/manager/npm/post-update/yarn.spec.ts +++ b/lib/manager/npm/post-update/yarn.spec.ts @@ -71,7 +71,7 @@ describe('manager/npm/post-update/yarn', () => { ); expect(fs.readFile).toHaveBeenCalledTimes(expectedFsCalls); expect(fs.remove).toHaveBeenCalledTimes(0); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); } ); @@ -90,7 +90,7 @@ describe('manager/npm/post-update/yarn', () => { skipInstalls: false, }; const res = await yarnHelper.generateLockFile('some-dir', {}, config); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); }); @@ -108,7 +108,7 @@ describe('manager/npm/post-update/yarn', () => { managerData: { yarnZeroInstall: true }, }; const res = await yarnHelper.generateLockFile('some-dir', {}, config); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); }); @@ -143,7 +143,7 @@ describe('manager/npm/post-update/yarn', () => { isLockfileUpdate: true, }, ]); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); } ); @@ -166,7 +166,7 @@ describe('manager/npm/post-update/yarn', () => { isLockfileUpdate: true, }, ]); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); } ); @@ -201,7 +201,7 @@ describe('manager/npm/post-update/yarn', () => { ]); expect(fs.readFile).toHaveBeenCalledTimes(expectedFsCalls); expect(fs.remove).toHaveBeenCalledTimes(1); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); } ); @@ -236,7 +236,7 @@ describe('manager/npm/post-update/yarn', () => { newValue: '3.0.1', }, ]); - expect(res.lockFile).toEqual('package-lock-contents'); + expect(res.lockFile).toBe('package-lock-contents'); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); } ); @@ -252,7 +252,7 @@ describe('manager/npm/post-update/yarn', () => { const res = await yarnHelper.generateLockFile('some-dir', {}); expect(fs.readFile).toHaveBeenCalledTimes(2); expect(res.error).toBeTrue(); - expect(res.lockFile).not.toBeDefined(); + expect(res.lockFile).toBeUndefined(); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); }); diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts index 60d4fd550fde78..ebbb3588291d8f 100644 --- a/lib/manager/npm/post-update/yarn.ts +++ b/lib/manager/npm/post-update/yarn.ts @@ -2,7 +2,7 @@ import is from '@sindresorhus/is'; import { gte, minVersion, validRange } from 'semver'; import { quote } from 'shlex'; import { join } from 'upath'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { SYSTEM_INSUFFICIENT_DISK_SPACE, TEMPORARY_ERROR, @@ -10,7 +10,8 @@ import { import { id as npmId } from '../../../datasource/npm'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; -import { ExecOptions, exec } from '../../../util/exec'; +import { exec } from '../../../util/exec'; +import type { ExecOptions } from '../../../util/exec/types'; import { exists, readFile, remove, writeFile } from '../../../util/fs'; import { regEx } from '../../../util/regex'; import type { PostUpdateConfig, Upgrade } from '../../types'; @@ -128,7 +129,7 @@ export async function generateLockFile( extraEnv.YARN_ENABLE_GLOBAL_CACHE = '1'; } } - if (!getGlobalConfig().allowScripts || config.ignoreScripts) { + if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) { if (isYarn1) { cmdOptions += ' --ignore-scripts'; } else if (isYarnModeAvailable) { @@ -148,11 +149,11 @@ export async function generateLockFile( image: 'node', tagScheme: 'node', tagConstraint, - preCommands, }, + preCommands, }; // istanbul ignore if - if (getGlobalConfig().exposeAllEnv) { + if (GlobalConfig.get('exposeAllEnv')) { execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH; execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL; } diff --git a/lib/manager/npm/range.spec.ts b/lib/manager/npm/range.spec.ts index 89bda5778ae906..2508e6b94c5b5f 100644 --- a/lib/manager/npm/range.spec.ts +++ b/lib/manager/npm/range.spec.ts @@ -4,14 +4,14 @@ import { getRangeStrategy } from '.'; describe('manager/npm/range', () => { it('returns same if not auto', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('pins devDependencies', () => { const config: RangeConfig = { rangeStrategy: 'auto', depType: 'devDependencies', }; - expect(getRangeStrategy(config)).toEqual('pin'); + expect(getRangeStrategy(config)).toBe('pin'); }); it('pins app dependencies', () => { const config: RangeConfig = { @@ -19,14 +19,14 @@ describe('manager/npm/range', () => { depType: 'dependencies', packageJsonType: 'app', }; - expect(getRangeStrategy(config)).toEqual('pin'); + expect(getRangeStrategy(config)).toBe('pin'); }); it('widens peerDependencies', () => { const config: RangeConfig = { rangeStrategy: 'auto', depType: 'peerDependencies', }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('widens complex ranges', () => { const config: RangeConfig = { @@ -34,7 +34,7 @@ describe('manager/npm/range', () => { depType: 'dependencies', currentValue: '^1.6.0 || ^2.0.0', }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('widens complex bump', () => { const config: RangeConfig = { @@ -42,13 +42,13 @@ describe('manager/npm/range', () => { depType: 'dependencies', currentValue: '^1.6.0 || ^2.0.0', }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('defaults to replace', () => { const config: RangeConfig = { rangeStrategy: 'auto', depType: 'dependencies', }; - expect(getRangeStrategy(config)).toEqual('replace'); + expect(getRangeStrategy(config)).toBe('replace'); }); }); diff --git a/lib/manager/npm/update/dependency/index.spec.ts b/lib/manager/npm/update/dependency/index.spec.ts index f80a404095cb9b..29eb31e9f7d622 100644 --- a/lib/manager/npm/update/dependency/index.spec.ts +++ b/lib/manager/npm/update/dependency/index.spec.ts @@ -113,8 +113,8 @@ describe('manager/npm/update/dependency/index', () => { fileContent: input01Content, upgrade, }); - expect(JSON.parse(testContent).dependencies.config).toEqual('1.22.0'); - expect(JSON.parse(testContent).resolutions.config).toEqual('1.22.0'); + expect(JSON.parse(testContent).dependencies.config).toBe('1.22.0'); + expect(JSON.parse(testContent).resolutions.config).toBe('1.22.0'); }); it('updates glob resolutions', () => { const upgrade = { @@ -126,10 +126,8 @@ describe('manager/npm/update/dependency/index', () => { fileContent: input01GlobContent, upgrade, }); - expect(JSON.parse(testContent).dependencies.config).toEqual('1.22.0'); - expect(JSON.parse(testContent).resolutions['**/config']).toEqual( - '1.22.0' - ); + expect(JSON.parse(testContent).dependencies.config).toBe('1.22.0'); + expect(JSON.parse(testContent).resolutions['**/config']).toBe('1.22.0'); }); it('updates glob resolutions without dep', () => { const upgrade = { @@ -142,7 +140,7 @@ describe('manager/npm/update/dependency/index', () => { fileContent: input01Content, upgrade, }); - expect(JSON.parse(testContent).resolutions['**/@angular/cli']).toEqual( + expect(JSON.parse(testContent).resolutions['**/@angular/cli']).toBe( '8.1.0' ); }); @@ -210,5 +208,48 @@ describe('manager/npm/update/dependency/index', () => { }); expect(testContent).toEqual(outputContent); }); + + it('returns null if empty file', () => { + const upgrade = { + depType: 'dependencies', + depName: 'angular-touch-not', + newValue: '1.5.8', + }; + const testContent = npmUpdater.updateDependency({ + fileContent: null, + upgrade, + }); + expect(testContent).toBeNull(); + }); + + it('replaces package', () => { + const upgrade = { + depType: 'dependencies', + depName: 'config', + newName: 'abc', + newValue: '2.0.0', + }; + const testContent = npmUpdater.updateDependency({ + fileContent: input01Content, + upgrade, + }); + expect(JSON.parse(testContent).dependencies.config).toBeUndefined(); + expect(JSON.parse(testContent).dependencies.abc).toBe('2.0.0'); + }); + + it('replaces glob package resolutions', () => { + const upgrade = { + depType: 'dependencies', + depName: 'config', + newName: 'abc', + newValue: '2.0.0', + }; + const testContent = npmUpdater.updateDependency({ + fileContent: input01GlobContent, + upgrade, + }); + expect(JSON.parse(testContent).resolutions.config).toBeUndefined(); + expect(JSON.parse(testContent).resolutions['**/abc']).toBe('2.0.0'); + }); }); }); diff --git a/lib/manager/npm/update/dependency/index.ts b/lib/manager/npm/update/dependency/index.ts index 67d2a211ae17c2..f39062de361f1a 100644 --- a/lib/manager/npm/update/dependency/index.ts +++ b/lib/manager/npm/update/dependency/index.ts @@ -9,19 +9,22 @@ function replaceAsString( fileContent: string, depType: string, depName: string, - oldVersion: string, + oldValue: string, newValue: string ): string | null { - // Update the file = this is what we want if (depType === 'packageManager') { - // eslint-disable-next-line no-param-reassign parsedContents[depType] = newValue; + } else if (depName === oldValue) { + // The old value is the name of the dependency itself + delete Object.assign(parsedContents[depType], { + [newValue]: parsedContents[depType][oldValue], + })[oldValue]; } else { - // eslint-disable-next-line no-param-reassign + // The old value is the version of the dependency parsedContents[depType][depName] = newValue; } // Look for the old version number - const searchString = `"${oldVersion}"`; + const searchString = `"${oldValue}"`; const newString = `"${newValue}"`; // Skip ahead to depType section let searchIndex = fileContent.indexOf(`"${depType}"`) + depType.length; @@ -96,6 +99,16 @@ export function updateDependency({ oldVersion, newValue ); + if (upgrade.newName) { + newFileContent = replaceAsString( + parsedContents, + newFileContent, + depType, + depName, + depName, + upgrade.newName + ); + } // istanbul ignore if if (!newFileContent) { logger.debug( @@ -132,6 +145,20 @@ export function updateDependency({ parsedContents.resolutions[depKey], newValue ); + if (upgrade.newName) { + if (depKey === `**/${depName}`) { + // handles the case where a replacement is in a resolution + upgrade.newName = `**/${upgrade.newName}`; + } + newFileContent = replaceAsString( + parsedContents, + newFileContent, + 'resolutions', + depKey, + depKey, + upgrade.newName + ); + } } } return newFileContent; diff --git a/lib/manager/npm/update/locked-dependency/index.spec.ts b/lib/manager/npm/update/locked-dependency/index.spec.ts index 910b6713d746ed..2e9431873381e1 100644 --- a/lib/manager/npm/update/locked-dependency/index.spec.ts +++ b/lib/manager/npm/update/locked-dependency/index.spec.ts @@ -77,7 +77,7 @@ describe('manager/npm/update/locked-dependency/index', () => { }); expect( JSON.parse(res['package-lock.json']).dependencies.mime.version - ).toEqual('1.2.12'); + ).toBe('1.2.12'); }); it('fails to remediate if parent dep cannot support', async () => { const acceptsModified = clone(acceptsJson); @@ -105,7 +105,7 @@ describe('manager/npm/update/locked-dependency/index', () => { const res = await updateLockedDependency(config); expect(res['package.json']).toContain('"express": "4.1.0"'); const packageLock = JSON.parse(res['package-lock.json']); - expect(packageLock.dependencies.express.version).toEqual('4.1.0'); + expect(packageLock.dependencies.express.version).toBe('4.1.0'); }); it('remediates mime', async () => { config.depName = 'mime'; @@ -129,8 +129,8 @@ describe('manager/npm/update/locked-dependency/index', () => { .reply(200, typeIsJson); const res = await updateLockedDependency(config); const packageLock = JSON.parse(res['package-lock.json']); - expect(packageLock.dependencies.mime.version).toEqual('1.4.1'); - expect(packageLock.dependencies.express.version).toEqual('4.16.0'); + expect(packageLock.dependencies.mime.version).toBe('1.4.1'); + expect(packageLock.dependencies.express.version).toBe('4.16.0'); expect(httpMock.getTrace()).toMatchSnapshot(); }); }); diff --git a/lib/manager/npm/update/locked-dependency/index.ts b/lib/manager/npm/update/locked-dependency/index.ts index f968303915510c..e915cdcfec6e65 100644 --- a/lib/manager/npm/update/locked-dependency/index.ts +++ b/lib/manager/npm/update/locked-dependency/index.ts @@ -1,3 +1,4 @@ +import detectIndent from 'detect-indent'; import type { PackageJson } from 'type-fest'; import { logger } from '../../../../logger'; import { api as semver } from '../../../../versioning/npm'; @@ -15,10 +16,7 @@ export function validateInputs(config: UpdateLockedConfig): boolean { return false; } if (!(semver.isVersion(currentVersion) && semver.isVersion(newVersion))) { - logger.warn( - { currentVersion, newVersion }, - 'Update versions are not valid' - ); + logger.warn({ config }, 'Update versions are not valid'); return false; } return true; @@ -46,6 +44,7 @@ export async function updateLockedDependency( } let packageJson: PackageJson; let packageLockJson: PackageLockOrEntry; + const detectedIndent = detectIndent(lockFileContent).indent || ' '; let newPackageJsonContent: string; try { packageJson = JSON.parse(packageFileContent); @@ -161,7 +160,11 @@ export async function updateLockedDependency( delete dependency.resolved; delete dependency.integrity; } - let newLockFileContent = JSON.stringify(packageLockJson, null, 2); + let newLockFileContent = JSON.stringify( + packageLockJson, + null, + detectedIndent + ); // iterate through the parent updates first for (const parentUpdate of parentUpdates) { const parentUpdateConfig = { diff --git a/lib/manager/npm/update/locked-dependency/parent-version.spec.ts b/lib/manager/npm/update/locked-dependency/parent-version.spec.ts index b69240da05c2cd..5ecfd4fee648f7 100644 --- a/lib/manager/npm/update/locked-dependency/parent-version.spec.ts +++ b/lib/manager/npm/update/locked-dependency/parent-version.spec.ts @@ -28,7 +28,7 @@ describe('manager/npm/update/locked-dependency/parent-version', () => { expect( await findFirstParentVersion('express', '4.0.0', 'send', '0.11.1') - ).toEqual('4.11.1'); + ).toBe('4.11.1'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -52,7 +52,7 @@ describe('manager/npm/update/locked-dependency/parent-version', () => { 'buffer-crc32', '10.0.0' ) - ).toEqual('4.9.1'); + ).toBe('4.9.1'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -73,7 +73,7 @@ describe('manager/npm/update/locked-dependency/parent-version', () => { expect( await findFirstParentVersion('express', '4.0.0', 'qs', '6.0.4') - ).toEqual('4.14.0'); + ).toBe('4.14.0'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -93,7 +93,7 @@ describe('manager/npm/update/locked-dependency/parent-version', () => { expect( await findFirstParentVersion('express', '4.16.1', 'type-is', '1.2.1') - ).toEqual('4.16.1'); + ).toBe('4.16.1'); expect(httpMock.getTrace()).toMatchSnapshot(); }); diff --git a/lib/manager/npm/update/package-version/index.ts b/lib/manager/npm/update/package-version/index.ts index 0689cd9109ab70..c28065f6350817 100644 --- a/lib/manager/npm/update/package-version/index.ts +++ b/lib/manager/npm/update/package-version/index.ts @@ -1,5 +1,6 @@ import { ReleaseType, inc } from 'semver'; import { logger } from '../../../../logger'; +import { regEx } from '../../../../util/regex'; import type { BumpPackageVersionResult } from '../../../types'; export function bumpPackageVersion( @@ -31,7 +32,7 @@ export function bumpPackageVersion( } logger.debug({ newPjVersion }); bumpedContent = content.replace( - /(?"version":\s*")[^"]*/, // TODO #12070 + regEx(`(?"version":\\s*")[^"]*`), `$${newPjVersion}` ); if (bumpedContent === content) { diff --git a/lib/manager/nuget/__fixtures__/dotnet-tools.json b/lib/manager/nuget/__fixtures__/dotnet-tools.json new file mode 100644 index 00000000000000..7afd86c8f15236 --- /dev/null +++ b/lib/manager/nuget/__fixtures__/dotnet-tools.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "minver-cli": { + "version": "2.0.0", + "commands": ["minver"] + } + } +} diff --git a/lib/manager/nuget/__fixtures__/with-whitespaces/NuGet.config b/lib/manager/nuget/__fixtures__/with-whitespaces/NuGet.config new file mode 100644 index 00000000000000..cc8abe128eb32c --- /dev/null +++ b/lib/manager/nuget/__fixtures__/with-whitespaces/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lib/manager/nuget/__fixtures__/with-whitespaces/with-whitespaces.csproj b/lib/manager/nuget/__fixtures__/with-whitespaces/with-whitespaces.csproj new file mode 100644 index 00000000000000..872e2011797349 --- /dev/null +++ b/lib/manager/nuget/__fixtures__/with-whitespaces/with-whitespaces.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.1 + true + + + + + + + diff --git a/lib/manager/nuget/__snapshots__/artifacts.spec.ts.snap b/lib/manager/nuget/__snapshots__/artifacts.spec.ts.snap index da697c5b33d149..380b45dbb18229 100644 --- a/lib/manager/nuget/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/nuget/__snapshots__/artifacts.spec.ts.snap @@ -66,17 +66,6 @@ Array [ ] `; -exports[`manager/nuget/artifacts catches errors 1`] = ` -Array [ - Object { - "artifactError": Object { - "lockFile": "packages.lock.json", - "stderr": "not found", - }, - }, -] -`; - exports[`manager/nuget/artifacts does not update lock file when no deps changed 1`] = `Array []`; exports[`manager/nuget/artifacts does not update lock file when non-proj file is changed 1`] = `Array []`; diff --git a/lib/manager/nuget/__snapshots__/extract.spec.ts.snap b/lib/manager/nuget/__snapshots__/extract.spec.ts.snap index f37a61bb873712..460af6dfa98c69 100644 --- a/lib/manager/nuget/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/nuget/__snapshots__/extract.spec.ts.snap @@ -1,86 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/nuget/extract extractPackageFile() .config/dotnet-tools.json with-config 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.0.0", - "datasource": "nuget", - "depName": "minver-cli", - "depType": "nuget", - "registryUrls": Array [ - "https://api.nuget.org/v3/index.json#protocolVersion=3", - "https://contoso.com/packages/", - ], - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() .config/dotnet-tools.json works 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "2.0.0", - "datasource": "nuget", - "depName": "minver-cli", - "depType": "nuget", - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() considers NuGet.config 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - "registryUrls": Array [ - "https://api.nuget.org/v3/index.json#protocolVersion=3", - "https://contoso.com/packages/", - ], - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() considers lower-case nuget.config 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - "registryUrls": Array [ - "https://api.nuget.org/v3/index.json#protocolVersion=3", - "https://contoso.com/packages/", - ], - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() considers pascal-case NuGet.Config 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - "registryUrls": Array [ - "https://api.nuget.org/v3/index.json#protocolVersion=3", - "https://contoso.com/packages/", - ], - }, - ], -} -`; - exports[`manager/nuget/extract extractPackageFile() extracts all dependencies 1`] = ` Array [ Object { @@ -305,85 +224,3 @@ Array [ }, ] `; - -exports[`manager/nuget/extract extractPackageFile() extracts registry URLs independently 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - "registryUrls": Array [ - "https://api.nuget.org/v3/index.json", - "https://example.org/one", - ], - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() extracts registry URLs independently 2`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - "registryUrls": Array [ - "https://api.nuget.org/v3/index.json", - "https://example.org/two", - ], - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() handles NuGet.config without package sources 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() handles malformed NuGet.config 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() ignores local feed in NuGet.config 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": "4.5.0", - "datasource": "nuget", - "depName": "Autofac", - "depType": "nuget", - "registryUrls": Array [ - "https://contoso.com/packages/", - ], - }, - ], -} -`; - -exports[`manager/nuget/extract extractPackageFile() returns empty for invalid csproj 1`] = ` -Object { - "deps": Array [], -} -`; diff --git a/lib/manager/nuget/artifacts.spec.ts b/lib/manager/nuget/artifacts.spec.ts index 0a692be8755f5f..71824a6dc72162 100644 --- a/lib/manager/nuget/artifacts.spec.ts +++ b/lib/manager/nuget/artifacts.spec.ts @@ -2,7 +2,7 @@ import { exec as _exec } from 'child_process'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { fs, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as docker from '../../util/exec/docker'; import * as _env from '../../util/exec/env'; @@ -49,12 +49,12 @@ describe('manager/nuget/artifacts', () => { Promise.resolve(`others/${dirName}`) ); getRandomString.mockReturnValue('not-so-random' as any); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('aborts if no lock file found', async () => { @@ -150,7 +150,7 @@ describe('manager/nuget/artifacts', () => { }); it('supports docker mode', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); fs.readLocalFile.mockResolvedValueOnce('Current packages.lock.json' as any); @@ -166,7 +166,7 @@ describe('manager/nuget/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('supports global mode', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'global' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); fs.readLocalFile.mockResolvedValueOnce('Current packages.lock.json' as any); @@ -187,7 +187,6 @@ describe('manager/nuget/artifacts', () => { fs.writeLocalFile.mockImplementationOnce(() => { throw new Error('not found'); }); - // FIXME: explicit assert condition expect( await nuget.updateArtifacts({ packageFileName: 'project.csproj', @@ -195,7 +194,14 @@ describe('manager/nuget/artifacts', () => { newPackageFileContent: '{}', config, }) - ).toMatchSnapshot(); + ).toEqual([ + { + artifactError: { + lockFile: 'packages.lock.json', + stderr: 'not found', + }, + }, + ]); }); it('authenticates at registries', async () => { const execSnapshots = mockExecAll(exec); diff --git a/lib/manager/nuget/artifacts.ts b/lib/manager/nuget/artifacts.ts index 34a910aad3c6c7..3ff382af536705 100644 --- a/lib/manager/nuget/artifacts.ts +++ b/lib/manager/nuget/artifacts.ts @@ -1,9 +1,11 @@ import { join } from 'path'; -import { getGlobalConfig } from '../../config/global'; +import { quote } from 'shlex'; +import { GlobalConfig } from '../../config/global'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { id, parseRegistryUrl } from '../../datasource/nuget'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { ensureCacheDir, getSiblingFileName, @@ -30,7 +32,7 @@ async function addSourceCmds( config: UpdateArtifactsConfig, nugetConfigFile: string ): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const registries = (await getConfiguredRegistries(packageFileName, localDir)) || getDefaultRegistries(); @@ -44,7 +46,7 @@ async function addSourceCmds( let addSourceCmd = `dotnet nuget add source ${registryInfo.feedUrl} --configfile ${nugetConfigFile}`; if (registry.name) { // Add name for registry, if known. - addSourceCmd += ` --name ${registry.name}`; + addSourceCmd += ` --name ${quote(registry.name)}`; } if (username && password) { // Add registry credentials from host rules, if configured. diff --git a/lib/manager/nuget/extract.spec.ts b/lib/manager/nuget/extract.spec.ts index 69fa7982637ca8..d12fc9b4e69adb 100644 --- a/lib/manager/nuget/extract.spec.ts +++ b/lib/manager/nuget/extract.spec.ts @@ -1,6 +1,6 @@ import * as upath from 'upath'; import { loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import type { ExtractConfig } from '../types'; import { extractPackageFile } from './extract'; @@ -14,16 +14,15 @@ const adminConfig: RepoGlobalConfig = { describe('manager/nuget/extract', () => { describe('extractPackageFile()', () => { beforeEach(() => { - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns empty for invalid csproj', async () => { - // FIXME: explicit assert condition - expect( - await extractPackageFile('nothing here', 'bogus', config) - ).toMatchSnapshot(); + expect(await extractPackageFile('nothing here', 'bogus', config)).toEqual( + { deps: [] } + ); }); it('extracts package version dependency', async () => { const packageFile = @@ -50,68 +49,160 @@ describe('manager/nuget/extract', () => { it('considers NuGet.config', async () => { const packageFile = 'with-config-file/with-config-file.csproj'; const contents = loadFixture(packageFile); - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + registryUrls: [ + 'https://api.nuget.org/v3/index.json#protocolVersion=3', + 'https://contoso.com/packages/', + ], + }, + ], + }); }); it('considers lower-case nuget.config', async () => { const packageFile = 'with-lower-case-config-file/with-lower-case-config-file.csproj'; const contents = loadFixture(packageFile); - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + registryUrls: [ + 'https://api.nuget.org/v3/index.json#protocolVersion=3', + 'https://contoso.com/packages/', + ], + }, + ], + }); }); it('considers pascal-case NuGet.Config', async () => { const packageFile = 'with-pascal-case-config-file/with-pascal-case-config-file.csproj'; const contents = loadFixture(packageFile); - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + registryUrls: [ + 'https://api.nuget.org/v3/index.json#protocolVersion=3', + 'https://contoso.com/packages/', + ], + }, + ], + }); }); it('handles malformed NuGet.config', async () => { const packageFile = 'with-malformed-config-file/with-malformed-config-file.csproj'; const contents = loadFixture(packageFile); - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + }, + ], + }); }); it('handles NuGet.config without package sources', async () => { const packageFile = 'without-package-sources/without-package-sources.csproj'; const contents = loadFixture(packageFile); - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + }, + ], + }); }); + + it('handles NuGet.config with whitespaces in package source keys', async () => { + const packageFile = 'with-whitespaces/with-whitespaces.csproj'; + const contents = loadFixture(packageFile); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '12.0.3', + datasource: 'nuget', + depName: 'Newtonsoft.Json', + depType: 'nuget', + registryUrls: [ + 'https://api.nuget.org/v3/index.json#protocolVersion=3', + 'https://my.myget.org/F/my/auth/guid/api/v3/index.json', + ], + }, + ], + }); + }); + it('ignores local feed in NuGet.config', async () => { const packageFile = 'with-local-feed-in-config-file/with-local-feed-in-config-file.csproj'; const contents = loadFixture(packageFile); - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + registryUrls: ['https://contoso.com/packages/'], + }, + ], + }); }); it('extracts registry URLs independently', async () => { const packageFile = 'multiple-package-files/one/one.csproj'; const contents = loadFixture(packageFile); const otherPackageFile = 'multiple-package-files/two/two.csproj'; const otherContents = loadFixture(otherPackageFile); - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + registryUrls: [ + 'https://api.nuget.org/v3/index.json', + 'https://example.org/one', + ], + }, + ], + }); expect( await extractPackageFile(otherContents, otherPackageFile, config) - ).toMatchSnapshot(); + ).toEqual({ + deps: [ + { + currentValue: '4.5.0', + datasource: 'nuget', + depName: 'Autofac', + depType: 'nuget', + registryUrls: [ + 'https://api.nuget.org/v3/index.json', + 'https://example.org/two', + ], + }, + ], + }); }); it('extracts msbuild-sdks from global.json', async () => { @@ -172,33 +263,44 @@ describe('manager/nuget/extract', () => { describe('.config/dotnet-tools.json', () => { const packageFile = '.config/dotnet-tools.json'; - const contents = `{ - "version": 1, - "isRoot": true, - "tools": { - "minver-cli": { - "version": "2.0.0", - "commands": ["minver"] - } - } -}`; + const contents = loadFixture('dotnet-tools.json'); it('works', async () => { - // FIXME: explicit assert condition - expect( - await extractPackageFile(contents, packageFile, config) - ).toMatchSnapshot(); + expect(await extractPackageFile(contents, packageFile, config)).toEqual( + { + deps: [ + { + currentValue: '2.0.0', + datasource: 'nuget', + depName: 'minver-cli', + depType: 'nuget', + }, + ], + } + ); }); it('with-config', async () => { - // FIXME: explicit assert condition expect( await extractPackageFile( contents, `with-config-file/${packageFile}`, config ) - ).toMatchSnapshot(); + ).toEqual({ + deps: [ + { + currentValue: '2.0.0', + datasource: 'nuget', + depName: 'minver-cli', + depType: 'nuget', + registryUrls: [ + 'https://api.nuget.org/v3/index.json#protocolVersion=3', + 'https://contoso.com/packages/', + ], + }, + ], + }); }); it('wrong version', async () => { diff --git a/lib/manager/nuget/extract.ts b/lib/manager/nuget/extract.ts index a5e6ab86a7f2d2..5c14279e91993f 100644 --- a/lib/manager/nuget/extract.ts +++ b/lib/manager/nuget/extract.ts @@ -1,9 +1,10 @@ import { XmlDocument, XmlElement, XmlNode } from 'xmldoc'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import * as datasourceNuget from '../../datasource/nuget'; import { logger } from '../../logger'; import { getSiblingFileName, localPathExists } from '../../util/fs'; import { hasKey } from '../../util/object'; +import { regEx } from '../../util/regex'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types'; import { extractMsbuildGlobalManifest } from './extract/global-manifest'; import type { DotnetToolsManifest } from './types'; @@ -20,8 +21,9 @@ import { getConfiguredRegistries } from './util'; * The update of the right boundary does not make sense regarding to the lowest version restore rule, * so we don't include it in the extracting regexp */ -const checkVersion = - /^\s*(?:[[])?(?:(?[^"(,[\]]+)\s*(?:,\s*[)\]]|])?)\s*$/; // TODO #12070 +const checkVersion = regEx( + `^\\s*(?:[[])?(?:(?[^"(,[\\]]+)\\s*(?:,\\s*[)\\]]|])?)\\s*$` +); const elemNames = new Set([ 'PackageReference', 'PackageVersion', @@ -72,13 +74,13 @@ export async function extractPackageFile( ): Promise { logger.trace({ packageFile }, 'nuget.extractPackageFile()'); - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const registries = await getConfiguredRegistries(packageFile, localDir); const registryUrls = registries ? registries.map((registry) => registry.url) : undefined; - if (packageFile.endsWith('.config/dotnet-tools.json')) { + if (packageFile.endsWith('dotnet-tools.json')) { const deps: PackageDependency[] = []; let manifest: DotnetToolsManifest; diff --git a/lib/manager/nuget/index.ts b/lib/manager/nuget/index.ts index c5e1689cc7304f..35f875646841c3 100644 --- a/lib/manager/nuget/index.ts +++ b/lib/manager/nuget/index.ts @@ -9,7 +9,7 @@ export const defaultConfig = { fileMatch: [ '\\.(?:cs|fs|vb)proj$', '\\.(?:props|targets)$', - '\\.config\\/dotnet-tools\\.json$', - '(^|//)global\\.json$', + '(^|\\/)dotnet-tools\\.json$', + '(^|\\/)global\\.json$', ], }; diff --git a/lib/manager/nuget/readme.md b/lib/manager/nuget/readme.md index d8d14edd29bc8c..9ee0df70d66bb3 100644 --- a/lib/manager/nuget/readme.md +++ b/lib/manager/nuget/readme.md @@ -2,4 +2,4 @@ The `nuget` configuration object is used to control settings for the NuGet packa The NuGet package manager supports a SDK-style `.csproj`/`.fsproj`/`.vbproj`/`.props`/`.targets` format, as described [here](https://natemcmaster.com/blog/2017/03/09/vs2015-to-vs2017-upgrade/). This means that .NET Core projects are all supported but any .NET Framework projects need to be updated to the new `.csproj`/`.fsproj`/`.vbproj`/`.props`/`.targets` format in order to be detected and supported by Renovate. -The NuGet manager also supports `global.json` and `.config/dotnet-tools.json` SDK files. +The NuGet manager also supports `global.json` and `dotnet-tools.json` SDK files. diff --git a/lib/manager/pip-compile/__snapshots__/artifacts.spec.ts.snap b/lib/manager/pip-compile/__snapshots__/artifacts.spec.ts.snap index 84fa937c6c6d0c..29b95f564dbac9 100644 --- a/lib/manager/pip-compile/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/pip-compile/__snapshots__/artifacts.spec.ts.snap @@ -1,16 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/pip-compile/artifacts catches errors 1`] = ` -Array [ - Object { - "artifactError": Object { - "lockFile": "requirements.txt", - "stderr": "not found", - }, - }, -] -`; - exports[`manager/pip-compile/artifacts returns null if unchanged 1`] = ` Array [ Object { diff --git a/lib/manager/pip-compile/artifacts.spec.ts b/lib/manager/pip-compile/artifacts.spec.ts index 3c64357c336085..6b37eb0e079291 100644 --- a/lib/manager/pip-compile/artifacts.spec.ts +++ b/lib/manager/pip-compile/artifacts.spec.ts @@ -3,11 +3,11 @@ import _fs from 'fs-extra'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { git, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as docker from '../../util/exec/docker'; import * as _env from '../../util/exec/env'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import type { UpdateArtifactsConfig } from '../types'; import * as pipCompile from './artifacts'; @@ -40,7 +40,7 @@ describe('manager/pip-compile/artifacts', () => { LANG: 'en_US.UTF-8', LC_ALL: 'en_US', }); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); @@ -89,7 +89,7 @@ describe('manager/pip-compile/artifacts', () => { }); it('supports docker mode', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); const execSnapshots = mockExecAll(exec); git.getRepoStatus.mockResolvedValue({ modified: ['requirements.txt'], @@ -111,7 +111,6 @@ describe('manager/pip-compile/artifacts', () => { fs.outputFile.mockImplementationOnce(() => { throw new Error('not found'); }); - // FIXME: explicit assert condition expect( await pipCompile.updateArtifacts({ packageFileName: 'requirements.in', @@ -119,7 +118,11 @@ describe('manager/pip-compile/artifacts', () => { newPackageFileContent: '{}', config, }) - ).toMatchSnapshot(); + ).toEqual([ + { + artifactError: { lockFile: 'requirements.txt', stderr: 'not found' }, + }, + ]); }); it('returns updated requirements.txt when doing lockfile maintenance', async () => { @@ -141,7 +144,7 @@ describe('manager/pip-compile/artifacts', () => { }); it('uses pipenv version from config', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); const execSnapshots = mockExecAll(exec); git.getRepoStatus.mockResolvedValue({ modified: ['requirements.txt'], diff --git a/lib/manager/pip-compile/artifacts.ts b/lib/manager/pip-compile/artifacts.ts index c5809a31e673ce..a2025ace6eb4f8 100644 --- a/lib/manager/pip-compile/artifacts.ts +++ b/lib/manager/pip-compile/artifacts.ts @@ -3,7 +3,8 @@ import { quote } from 'shlex'; import { parse } from 'upath'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { deleteLocalFile, readLocalFile, writeLocalFile } from '../../util/fs'; import { getRepoStatus } from '../../util/git'; import { regEx } from '../../util/regex'; @@ -67,10 +68,10 @@ export async function updateArtifacts({ image: 'python', tagConstraint, tagScheme: 'pep440', - preCommands: [ - `pip install --user ${quote(`pip-tools${pipToolsConstraint}`)}`, - ], }, + preCommands: [ + `pip install --user ${quote(`pip-tools${pipToolsConstraint}`)}`, + ], }; logger.debug({ cmd }, 'pip-compile command'); await exec(cmd, execOptions); diff --git a/lib/manager/pip_requirements/artifacts.spec.ts b/lib/manager/pip_requirements/artifacts.spec.ts index 798025ef7a0710..61af191fa3b3ab 100644 --- a/lib/manager/pip_requirements/artifacts.spec.ts +++ b/lib/manager/pip_requirements/artifacts.spec.ts @@ -1,5 +1,5 @@ import _fs from 'fs-extra'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { UpdateArtifactsConfig } from '../types'; import { updateArtifacts } from './artifacts'; @@ -19,7 +19,7 @@ describe('manager/pip_requirements/artifacts', () => { beforeEach(() => { jest.resetAllMocks(); jest.resetModules(); - setGlobalConfig({ localDir: '' }); + GlobalConfig.set({ localDir: '' }); }); it('returns null if no updatedDeps were provided', async () => { expect( diff --git a/lib/manager/pip_requirements/artifacts.ts b/lib/manager/pip_requirements/artifacts.ts index 4725a5a938f5d2..d3ca1878f036ae 100644 --- a/lib/manager/pip_requirements/artifacts.ts +++ b/lib/manager/pip_requirements/artifacts.ts @@ -1,7 +1,8 @@ import is from '@sindresorhus/is'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { readLocalFile } from '../../util/fs'; import { regEx } from '../../util/regex'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; @@ -40,8 +41,8 @@ export async function updateArtifacts({ docker: { image: 'python', tagScheme: 'pip_requirements', - preCommands: ['pip install hashin'], }, + preCommands: ['pip install hashin'], }; await exec(cmd, execOptions); const newContent = await readLocalFile(packageFileName, 'utf8'); diff --git a/lib/manager/pip_requirements/extract.spec.ts b/lib/manager/pip_requirements/extract.spec.ts index 8f28c14fcb1c74..94f9b027834b96 100644 --- a/lib/manager/pip_requirements/extract.spec.ts +++ b/lib/manager/pip_requirements/extract.spec.ts @@ -1,5 +1,5 @@ import { loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { extractPackageFile } from './extract'; const requirements1 = loadFixture('requirements1.txt'); @@ -14,11 +14,11 @@ const requirements8 = loadFixture('requirements8.txt'); describe('manager/pip_requirements/extract', () => { beforeEach(() => { delete process.env.PIP_TEST_TOKEN; - setGlobalConfig(); + GlobalConfig.reset(); }); afterEach(() => { delete process.env.PIP_TEST_TOKEN; - setGlobalConfig(); + GlobalConfig.reset(); }); describe('extractPackageFile()', () => { let config; @@ -115,16 +115,16 @@ describe('manager/pip_requirements/extract', () => { expect(res.registryUrls).toEqual([ 'https://pypi.org/pypi/', 'http://$PIP_TEST_TOKEN:example.com/private-pypi/', - // eslint-disable-next-line no-template-curly-in-string + 'http://${PIP_TEST_TOKEN}:example.com/private-pypi/', 'http://$PIP_TEST_TOKEN:example.com/private-pypi/', - // eslint-disable-next-line no-template-curly-in-string + 'http://${PIP_TEST_TOKEN}:example.com/private-pypi/', ]); }); it('should replace env vars in high trust mode', () => { process.env.PIP_TEST_TOKEN = 'its-a-secret'; - setGlobalConfig({ exposeAllEnv: true }); + GlobalConfig.set({ exposeAllEnv: true }); const res = extractPackageFile(requirements7, 'unused_file_name', {}); expect(res.registryUrls).toEqual([ 'https://pypi.org/pypi/', diff --git a/lib/manager/pip_requirements/extract.ts b/lib/manager/pip_requirements/extract.ts index 047fbcfe9dda49..c10ab1966e8dcf 100644 --- a/lib/manager/pip_requirements/extract.ts +++ b/lib/manager/pip_requirements/extract.ts @@ -1,6 +1,6 @@ // based on https://www.python.org/dev/peps/pep-0508/#names import { RANGE_PATTERN } from '@renovate/pep440/lib/specifier'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { PypiDatasource } from '../../datasource/pypi'; import { logger } from '../../logger'; import { SkipReason } from '../../types'; @@ -91,7 +91,7 @@ export function extractPackageFile( res.registryUrls = registryUrls.map((url) => { // handle the optional quotes in eg. `--extra-index-url "https://foo.bar"` const cleaned = url.replace(regEx(/^"/), '').replace(regEx(/"$/), ''); // TODO #12071 - if (!getGlobalConfig().exposeAllEnv) { + if (!GlobalConfig.get('exposeAllEnv')) { return cleaned; } // interpolate any environment variables diff --git a/lib/manager/pip_requirements/range.spec.ts b/lib/manager/pip_requirements/range.spec.ts index 00d2861330cb46..80fbe2b14a0fba 100644 --- a/lib/manager/pip_requirements/range.spec.ts +++ b/lib/manager/pip_requirements/range.spec.ts @@ -4,10 +4,10 @@ import { getRangeStrategy } from '.'; describe('manager/pip_requirements/range', () => { it('returns same if not auto', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('pins if auto', () => { const config: RangeConfig = { rangeStrategy: 'auto' }; - expect(getRangeStrategy(config)).toEqual('pin'); + expect(getRangeStrategy(config)).toBe('pin'); }); }); diff --git a/lib/manager/pip_setup/__snapshots__/extract.spec.ts.snap b/lib/manager/pip_setup/__snapshots__/extract.spec.ts.snap index cfeee48ee63fd5..c52a8bf3f3de47 100644 --- a/lib/manager/pip_setup/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/pip_setup/__snapshots__/extract.spec.ts.snap @@ -1,62 +1,129 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/pip_setup/extract getPythonAlias returns the python alias to use 1`] = `"python3.8"`; - -exports[`manager/pip_setup/extract getPythonAlias returns the python alias to use 2`] = ` -Array [ - Object { - "cmd": "python --version", - "options": Object { - "cwd": "/tmp/foo/bar", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3 --version", - "options": Object { - "cwd": "/tmp/foo/bar", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3.8 --version", - "options": Object { - "cwd": "/tmp/foo/bar", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] +exports[`manager/pip_setup/extract extractPackageFile() returns found deps 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": ">=3.1.13.0,<5.0", + "datasource": "pypi", + "depName": "celery", + "managerData": Object { + "lineNumber": 49, + }, + }, + Object { + "currentValue": ">=1.7", + "datasource": "pypi", + "depName": "logging_tree", + "managerData": Object { + "lineNumber": 52, + }, + }, + Object { + "currentValue": ">=2.2", + "datasource": "pypi", + "depName": "pygments", + "managerData": Object { + "lineNumber": 53, + }, + }, + Object { + "currentValue": ">=5.0", + "datasource": "pypi", + "depName": "psutil", + "managerData": Object { + "lineNumber": 54, + }, + }, + Object { + "currentValue": ">=3.0", + "datasource": "pypi", + "depName": "objgraph", + "managerData": Object { + "lineNumber": 55, + }, + }, + Object { + "currentValue": ">=1.11.23,<2.0", + "datasource": "pypi", + "depName": "django", + "managerData": Object { + "lineNumber": 58, + }, + }, + Object { + "currentValue": ">=0.11,<2.0", + "datasource": "pypi", + "depName": "flask", + "managerData": Object { + "lineNumber": 61, + }, + }, + Object { + "currentValue": ">=1.4,<2.0", + "datasource": "pypi", + "depName": "blinker", + "managerData": Object { + "lineNumber": 62, + }, + }, + Object { + "currentValue": ">=19.7.0,<20.0", + "datasource": "pypi", + "depName": "gunicorn", + "managerData": Object { + "lineNumber": 74, + }, + }, + Object { + "currentValue": ">=0.15.3,<0.16", + "datasource": "pypi", + "depName": "Werkzeug", + "managerData": Object { + "lineNumber": 75, + }, + }, + Object { + "currentValue": ">=3.2.1,<4.0", + "datasource": "pypi", + "depName": "statsd", + "managerData": Object { + "lineNumber": 75, + }, + }, + Object { + "currentValue": ">=2.10.0,<3.0", + "datasource": "pypi", + "depName": "requests", + "managerData": Object { + "lineNumber": 76, + }, + "skipReason": "ignored", + }, + Object { + "currentValue": ">=5.27.1,<7.0", + "datasource": "pypi", + "depName": "raven", + "managerData": Object { + "lineNumber": 77, + }, + }, + Object { + "currentValue": ">=0.15.2,<0.17", + "datasource": "pypi", + "depName": "future", + "managerData": Object { + "lineNumber": 78, + }, + }, + Object { + "currentValue": ">=1.0.16,<2.0", + "datasource": "pypi", + "depName": "ipaddress", + "managerData": Object { + "lineNumber": 79, + }, + }, + ], +} `; diff --git a/lib/manager/pip_setup/__snapshots__/index.spec.ts.snap b/lib/manager/pip_setup/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 540026674ee265..00000000000000 --- a/lib/manager/pip_setup/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,529 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`manager/pip_setup/index extractPackageFile() catches error 1`] = ` -Array [ - Object { - "cmd": "python --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3 --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3.8 --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3.9 --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": " \\"folders/foobar.py\\"", - "options": Object { - "cwd": "/tmp/github/some/repo/folders", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 30000, - }, - }, -] -`; - -exports[`manager/pip_setup/index extractPackageFile() returns found deps (docker) 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": ">=3.1.13.0,<5.0", - "datasource": "pypi", - "depName": "celery", - "managerData": Object { - "lineNumber": 49, - }, - }, - Object { - "currentValue": ">=1.7", - "datasource": "pypi", - "depName": "logging_tree", - "managerData": Object { - "lineNumber": 52, - }, - }, - Object { - "currentValue": ">=2.2", - "datasource": "pypi", - "depName": "pygments", - "managerData": Object { - "lineNumber": 53, - }, - }, - Object { - "currentValue": ">=5.0", - "datasource": "pypi", - "depName": "psutil", - "managerData": Object { - "lineNumber": 54, - }, - }, - Object { - "currentValue": ">=3.0", - "datasource": "pypi", - "depName": "objgraph", - "managerData": Object { - "lineNumber": 55, - }, - }, - Object { - "currentValue": ">=1.11.23,<2.0", - "datasource": "pypi", - "depName": "django", - "managerData": Object { - "lineNumber": 58, - }, - }, - Object { - "currentValue": ">=0.11,<2.0", - "datasource": "pypi", - "depName": "flask", - "managerData": Object { - "lineNumber": 61, - }, - }, - Object { - "currentValue": ">=1.4,<2.0", - "datasource": "pypi", - "depName": "blinker", - "managerData": Object { - "lineNumber": 62, - }, - }, - Object { - "currentValue": ">=19.7.0,<20.0", - "datasource": "pypi", - "depName": "gunicorn", - "managerData": Object { - "lineNumber": 74, - }, - }, - Object { - "currentValue": ">=3.2.1,<4.0", - "datasource": "pypi", - "depName": "statsd", - "managerData": Object { - "lineNumber": 75, - }, - }, - Object { - "currentValue": ">=0.15.3,<0.16", - "datasource": "pypi", - "depName": "Werkzeug", - "managerData": Object { - "lineNumber": 75, - }, - }, - Object { - "currentValue": ">=2.10.0,<3.0", - "datasource": "pypi", - "depName": "requests", - "managerData": Object { - "lineNumber": 76, - }, - "skipReason": "ignored", - }, - Object { - "currentValue": ">=5.27.1,<7.0", - "datasource": "pypi", - "depName": "raven", - "managerData": Object { - "lineNumber": 77, - }, - }, - Object { - "currentValue": ">=0.15.2,<0.17", - "datasource": "pypi", - "depName": "future", - "managerData": Object { - "lineNumber": 78, - }, - }, - Object { - "currentValue": ">=1.0.16,<2.0", - "datasource": "pypi", - "depName": "ipaddress", - "managerData": Object { - "lineNumber": 79, - }, - }, - ], -} -`; - -exports[`manager/pip_setup/index extractPackageFile() returns found deps 1`] = ` -Object { - "deps": Array [ - Object { - "currentValue": ">=3.1.13.0,<5.0", - "datasource": "pypi", - "depName": "celery", - "managerData": Object { - "lineNumber": 49, - }, - }, - Object { - "currentValue": ">=1.7", - "datasource": "pypi", - "depName": "logging_tree", - "managerData": Object { - "lineNumber": 52, - }, - }, - Object { - "currentValue": ">=2.2", - "datasource": "pypi", - "depName": "pygments", - "managerData": Object { - "lineNumber": 53, - }, - }, - Object { - "currentValue": ">=5.0", - "datasource": "pypi", - "depName": "psutil", - "managerData": Object { - "lineNumber": 54, - }, - }, - Object { - "currentValue": ">=3.0", - "datasource": "pypi", - "depName": "objgraph", - "managerData": Object { - "lineNumber": 55, - }, - }, - Object { - "currentValue": ">=1.11.23,<2.0", - "datasource": "pypi", - "depName": "django", - "managerData": Object { - "lineNumber": 58, - }, - }, - Object { - "currentValue": ">=0.11,<2.0", - "datasource": "pypi", - "depName": "flask", - "managerData": Object { - "lineNumber": 61, - }, - }, - Object { - "currentValue": ">=1.4,<2.0", - "datasource": "pypi", - "depName": "blinker", - "managerData": Object { - "lineNumber": 62, - }, - }, - Object { - "currentValue": ">=19.7.0,<20.0", - "datasource": "pypi", - "depName": "gunicorn", - "managerData": Object { - "lineNumber": 74, - }, - }, - Object { - "currentValue": ">=3.2.1,<4.0", - "datasource": "pypi", - "depName": "statsd", - "managerData": Object { - "lineNumber": 75, - }, - }, - Object { - "currentValue": ">=0.15.3,<0.16", - "datasource": "pypi", - "depName": "Werkzeug", - "managerData": Object { - "lineNumber": 75, - }, - }, - Object { - "currentValue": ">=2.10.0,<3.0", - "datasource": "pypi", - "depName": "requests", - "managerData": Object { - "lineNumber": 76, - }, - "skipReason": "ignored", - }, - Object { - "currentValue": ">=5.27.1,<7.0", - "datasource": "pypi", - "depName": "raven", - "managerData": Object { - "lineNumber": 77, - }, - }, - Object { - "currentValue": ">=0.15.2,<0.17", - "datasource": "pypi", - "depName": "future", - "managerData": Object { - "lineNumber": 78, - }, - }, - Object { - "currentValue": ">=1.0.16,<2.0", - "datasource": "pypi", - "depName": "ipaddress", - "managerData": Object { - "lineNumber": 79, - }, - }, - ], -} -`; - -exports[`manager/pip_setup/index extractPackageFile() returns found deps 2`] = ` -Array [ - Object { - "cmd": "python --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3 --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": " \\"setup.py\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 30000, - }, - }, -] -`; - -exports[`manager/pip_setup/index extractPackageFile() returns no deps 1`] = ` -Array [ - Object { - "cmd": "python --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3 --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": " \\"setup.py\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 30000, - }, - }, -] -`; - -exports[`manager/pip_setup/index extractPackageFile() should return null for invalid file 1`] = ` -Array [ - Object { - "cmd": "python --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": "python3 --version", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, - Object { - "cmd": " \\"folders/foobar.py\\"", - "options": Object { - "cwd": "/tmp/github/some/repo/folders", - "encoding": "utf-8", - "env": Object { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 30000, - }, - }, -] -`; diff --git a/lib/manager/pip_setup/extract.spec.ts b/lib/manager/pip_setup/extract.spec.ts index 9af134d966c0c7..11a2a058801de2 100644 --- a/lib/manager/pip_setup/extract.spec.ts +++ b/lib/manager/pip_setup/extract.spec.ts @@ -1,45 +1,39 @@ -import { envMock, exec, mockExecSequence } from '../../../test/exec-util'; -import { env } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; -import { - getPythonAlias, - parsePythonVersion, - pythonVersions, - resetModule, -} from './extract'; +import { loadFixture } from '../../../test/util'; +import type { ExtractConfig } from '../types'; +import { extractPackageFile } from './extract'; -jest.mock('child_process'); -jest.mock('../../util/exec/env'); +const packageFile = 'setup.py'; + +const config: ExtractConfig = {}; describe('manager/pip_setup/extract', () => { - beforeEach(() => { - jest.resetAllMocks(); - jest.resetModules(); - resetModule(); + describe('extractPackageFile()', () => { + it('returns found deps', () => { + const content = loadFixture(packageFile); - env.getChildProcessEnv.mockReturnValue(envMock.basic); - setGlobalConfig({ localDir: '/tmp/foo/bar' }); - }); - describe('parsePythonVersion', () => { - it('returns major and minor version numbers', () => { - expect(parsePythonVersion('Python 2.7.15rc1')).toEqual([2, 7]); - }); - }); - describe('getPythonAlias', () => { - it('returns the python alias to use', async () => { - const execSnapshots = mockExecSequence(exec, [ - { stdout: '', stderr: 'Python 2.7.17\\n' }, - new Error(), - { stdout: 'Python 3.8.0\\n', stderr: '' }, - new Error(), - ]); - const result = await getPythonAlias(); - expect(pythonVersions).toContain(result); - // FIXME: explicit assert condition - expect(result).toMatchSnapshot(); - expect(await getPythonAlias()).toEqual(result); - expect(execSnapshots).toMatchSnapshot(); - expect(execSnapshots).toHaveLength(3); + expect(extractPackageFile(content, packageFile, config)).toMatchSnapshot({ + deps: [ + { depName: 'celery', currentValue: '>=3.1.13.0,<5.0' }, + { depName: 'logging_tree', currentValue: '>=1.7' }, + { depName: 'pygments', currentValue: '>=2.2' }, + { depName: 'psutil', currentValue: '>=5.0' }, + { depName: 'objgraph', currentValue: '>=3.0' }, + { depName: 'django', currentValue: '>=1.11.23,<2.0' }, + { depName: 'flask', currentValue: '>=0.11,<2.0' }, + { depName: 'blinker', currentValue: '>=1.4,<2.0' }, + { depName: 'gunicorn', currentValue: '>=19.7.0,<20.0' }, + { depName: 'Werkzeug', currentValue: '>=0.15.3,<0.16' }, + { depName: 'statsd', currentValue: '>=3.2.1,<4.0' }, + { + depName: 'requests', + currentValue: '>=2.10.0,<3.0', + skipReason: 'ignored', + }, + { depName: 'raven', currentValue: '>=5.27.1,<7.0' }, + { depName: 'future', currentValue: '>=0.15.2,<0.17' }, + { depName: 'ipaddress', currentValue: '>=1.0.16,<2.0' }, + ], + }); }); }); }); diff --git a/lib/manager/pip_setup/extract.ts b/lib/manager/pip_setup/extract.ts index 97cb24ce76bfd2..b0311ea716801f 100644 --- a/lib/manager/pip_setup/extract.ts +++ b/lib/manager/pip_setup/extract.ts @@ -1,140 +1,84 @@ -import { getGlobalConfig } from '../../config/global'; +import { RANGE_PATTERN } from '@renovate/pep440/lib/specifier'; +import { lang, lexer, query as q } from '@renovatebot/parser-utils'; import { PypiDatasource } from '../../datasource/pypi'; -import { logger } from '../../logger'; import { SkipReason } from '../../types'; -import { exec } from '../../util/exec'; -import { isSkipComment } from '../../util/ignore'; import { regEx } from '../../util/regex'; -import { dependencyPattern } from '../pip_requirements/extract'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types'; -import type { PythonSetup } from './types'; -import { getExtractFile, parseReport } from './util'; -export const pythonVersions = ['python', 'python3', 'python3.8', 'python3.9']; -let pythonAlias: string | null = null; - -export function resetModule(): void { - pythonAlias = null; +interface ManagerData { + lineNumber: number; } -export function parsePythonVersion(str: string): number[] { - const arr = str.split(' ')[1].split('.'); - return [parseInt(arr[0], 10), parseInt(arr[1], 10)]; -} +type Context = PackageFile; + +const python = lang.createLang('python'); -export async function getPythonAlias(): Promise { - if (pythonAlias) { - return pythonAlias; - } - pythonAlias = pythonVersions[0]; // fallback to 'python' - for (const pythonVersion of pythonVersions) { - try { - const { stdout, stderr } = await exec(`${pythonVersion} --version`); - const version = parsePythonVersion(stdout || stderr); - if (version[0] >= 3 && version[1] >= 7) { - pythonAlias = pythonVersion; - break; - } - } catch (err) { - logger.debug(`${pythonVersion} alias not found`); - } - } - return pythonAlias; +// Optimize regex memory usage when we don't need named groups +function cleanupNamedGroups(regexSource: string): string { + return regexSource.replace(/\(\?<\w+>/g, '(?:'); } -export async function extractSetupFile( - _content: string, - packageFile: string, - config: ExtractConfig -): Promise { - let cmd = 'python'; - const extractPy = await getExtractFile(); - const args = [`"${extractPy}"`, `"${packageFile}"`]; - if (getGlobalConfig().binarySource !== 'docker') { - logger.debug('Running python via global command'); - cmd = await getPythonAlias(); - } - logger.debug({ cmd, args }, 'python command'); - const res = await exec(`${cmd} ${args.join(' ')}`, { - cwdFile: packageFile, - timeout: 30000, - docker: { - image: 'python', +const rangePattern = cleanupNamedGroups(RANGE_PATTERN); +const versionPattern = `(?:${rangePattern}(?:\\s*,\\s*${rangePattern})*)`; +const depNamePattern = '(?:[a-zA-Z][-_a-zA-Z0-9]*[a-zA-Z0-9])'; +const depPattern = [ + '^', + `(?${depNamePattern})`, + `(?(?:\\[\\s*(?:${depNamePattern}(?:\\s*,\\s*${depNamePattern})*\\s*)\\])?)`, + `(?${versionPattern})`, +].join('\\s*'); + +const extractRegex = regEx(depPattern); + +// Extract dependency string +function depStringHandler( + ctx: Context, + token: lexer.StringValueToken +): Context { + const depStr = token.value; + const match = extractRegex.exec(depStr); + const { depName, currentValue } = match.groups; + + const dep: PackageDependency = { + depName, + currentValue, + managerData: { + lineNumber: token.line - 1, }, - }); - if (res.stderr) { - const stderr = res.stderr - .replace(regEx(/.*\n\s*import imp/), '') - .trim() - .replace('fatal: No names found, cannot describe anything.', ''); - if (stderr.length) { - logger.warn({ stdout: res.stdout, stderr }, 'Error in read setup file'); - } - } - return parseReport(packageFile); + datasource: PypiDatasource.id, + }; + + return { ...ctx, deps: [...ctx.deps, dep] }; +} + +// Add `skip-reason` for dependencies annotated +// with "# renovate: ignore" comment +function depSkipHandler(ctx: Context): Context { + const dep = ctx.deps[ctx.deps.length - 1]; + const deps = ctx.deps.slice(0, -1); + deps.push({ ...dep, skipReason: SkipReason.Ignored }); + return { ...ctx, deps }; } -export async function extractPackageFile( +const incompleteDepString = q + .str(new RegExp(cleanupNamedGroups(depPattern))) + .op(/^\+|\*$/); + +const depString = q + .str(new RegExp(cleanupNamedGroups(depPattern)), depStringHandler) + .opt( + q + .opt(q.op(',')) + .comment(/^#\s*renovate\s*:\s*ignore\s*$/, depSkipHandler) + ); + +const query = q.alt(incompleteDepString, depString); + +export function extractPackageFile( content: string, - packageFile: string, - config: ExtractConfig -): Promise { - logger.debug('pip_setup.extractPackageFile()'); - let setup: PythonSetup; - try { - setup = await extractSetupFile(content, packageFile, config); - } catch (err) { - logger.debug({ err, content, packageFile }, 'Failed to read setup.py file'); - } - if (!setup) { - return null; - } - const requires: string[] = []; - if (setup.install_requires) { - requires.push(...setup.install_requires); - } - if (setup.extras_require) { - for (const req of Object.values(setup.extras_require)) { - requires.push(...req); - } - } - const regex = regEx(`^${dependencyPattern}`); - const lines = content.split('\n'); - const deps = requires - .map((req) => { - const lineNumber = lines.findIndex((l) => l.includes(req)); - if (lineNumber === -1) { - return null; - } - const rawline = lines[lineNumber]; - let dep: PackageDependency = {}; - const [, comment] = rawline.split('#').map((part) => part.trim()); - if (isSkipComment(comment)) { - dep.skipReason = SkipReason.Ignored; - } - regex.lastIndex = 0; - const matches = regex.exec(req); - if (!matches) { - return null; - } - const [, depName, , currentValue] = matches; - dep = { - ...dep, - depName, - currentValue, - managerData: { lineNumber }, - datasource: PypiDatasource.id, - }; - return dep; - }) - .filter(Boolean) - .sort((a, b) => - a.managerData.lineNumber === b.managerData.lineNumber - ? a.depName.localeCompare(b.depName) - : a.managerData.lineNumber - b.managerData.lineNumber - ); - if (!deps.length) { - return null; - } - return { deps }; + _packageFile: string, + _config: ExtractConfig +): PackageFile | null { + const res = python.query(content, query, { deps: [] }); + return res?.deps?.length ? res : null; } diff --git a/lib/manager/pip_setup/index.spec.ts b/lib/manager/pip_setup/index.spec.ts deleted file mode 100644 index de5380d6bb3ba3..00000000000000 --- a/lib/manager/pip_setup/index.spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { - ExecSnapshots, - envMock, - exec, - mockExecAll, - mockExecSequence, -} from '../../../test/exec-util'; -import { env, loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; -import type { RepoGlobalConfig } from '../../config/types'; -import * as fs from '../../util/fs'; -import type { ExtractConfig } from '../types'; -import * as extract from './extract'; -import { extractPackageFile } from '.'; - -const packageFile = 'setup.py'; -const content = loadFixture(packageFile); -const jsonContent = loadFixture('setup.py.json'); - -const adminConfig: RepoGlobalConfig = { - localDir: '/tmp/github/some/repo', - cacheDir: '/tmp/renovate/cache', -}; - -const config: ExtractConfig = {}; - -jest.mock('child_process'); -jest.mock('../../util/exec/env'); - -const pythonVersionCallResults = [ - { stdout: '', stderr: 'Python 2.7.17\\n' }, - { stdout: 'Python 3.7.5\\n', stderr: '' }, -]; - -// TODO: figure out snapshot similarity for each CI platform (#9617) -const fixSnapshots = (snapshots: ExecSnapshots): ExecSnapshots => - snapshots.map((snapshot) => ({ - ...snapshot, - cmd: snapshot.cmd.replace(/^.*extract\.py"\s+/, ' '), - })); - -describe('manager/pip_setup/index', () => { - describe('extractPackageFile()', () => { - beforeEach(() => { - jest.resetAllMocks(); - jest.resetModules(); - extract.resetModule(); - - setGlobalConfig(adminConfig); - env.getChildProcessEnv.mockReturnValue(envMock.basic); - - // do not copy extract.py - jest.spyOn(fs, 'writeLocalFile').mockResolvedValue(); - }); - - afterEach(() => { - setGlobalConfig(); - }); - - it('returns found deps', async () => { - const execSnapshots = mockExecSequence(exec, [ - ...pythonVersionCallResults, - { - stdout: '', - stderr: - 'DeprecationWarning: the imp module is deprecated in favour of importlib', - }, - ]); - jest.spyOn(fs, 'readLocalFile').mockResolvedValueOnce(jsonContent); - // FIXME: explicit assert condition - expect( - await extractPackageFile(content, packageFile, config) - ).toMatchSnapshot(); - expect(exec).toHaveBeenCalledTimes(3); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); - - it('returns found deps (docker)', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); - const execSnapshots = mockExecAll(exec, { stdout: '', stderr: '' }); - - jest.spyOn(fs, 'readLocalFile').mockResolvedValueOnce(jsonContent); - expect( - await extractPackageFile(content, packageFile, config) - ).toMatchSnapshot(); - expect(execSnapshots).toHaveLength(3); // TODO: figure out volume arguments in Windows (#9617) - }); - - it('returns no deps', async () => { - const execSnapshots = mockExecSequence(exec, [ - ...pythonVersionCallResults, - { - stdout: '', - stderr: 'fatal: No names found, cannot describe anything.', - }, - ]); - jest.spyOn(fs, 'readLocalFile').mockResolvedValueOnce('{}'); - expect(await extractPackageFile(content, packageFile, config)).toBeNull(); - expect(exec).toHaveBeenCalledTimes(3); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); - - it('should return null for invalid file', async () => { - const execSnapshots = mockExecSequence(exec, [ - ...pythonVersionCallResults, - new Error(), - ]); - expect( - await extractPackageFile( - 'raise Exception()', - 'folders/foobar.py', - config - ) - ).toBeNull(); - expect(exec).toHaveBeenCalledTimes(3); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); - it('catches error', async () => { - const execSnapshots = mockExecAll(exec, new Error()); - expect( - await extractPackageFile( - 'raise Exception()', - 'folders/foobar.py', - config - ) - ).toBeNull(); - expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); - }); - }); -}); diff --git a/lib/manager/pip_setup/util.ts b/lib/manager/pip_setup/util.ts deleted file mode 100644 index e2ec6245869176..00000000000000 --- a/lib/manager/pip_setup/util.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { dirname } from 'path'; -import { join } from 'upath'; -import dataFiles from '../../data-files.generated'; -import { ensureCacheDir, outputFile, readLocalFile } from '../../util/fs'; -import type { PythonSetup } from './types'; - -// need to match filename in `data/extract.py` -const REPORT = 'renovate-pip_setup-report.json'; -const EXTRACT = 'renovate-pip_setup-extract.py'; - -let extractPy: string | undefined; - -export async function getExtractFile(): Promise { - if (extractPy) { - return extractPy; - } - - extractPy = join(await ensureCacheDir('pip_setup'), EXTRACT); - await outputFile(extractPy, dataFiles.get('data/extract.py')); - - return extractPy; -} - -export async function parseReport(packageFile: string): Promise { - const data = await readLocalFile(join(dirname(packageFile), REPORT), 'utf8'); - return JSON.parse(data); -} diff --git a/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap b/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap index ba04fe5ad957c7..d8693802137fa8 100644 --- a/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/pipenv/__snapshots__/artifacts.spec.ts.snap @@ -1,16 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/pipenv/artifacts catches errors 1`] = ` -Array [ - Object { - "artifactError": Object { - "lockFile": "Pipfile.lock", - "stderr": "not found", - }, - }, -] -`; - exports[`manager/pipenv/artifacts handles no constraint 1`] = ` Array [ Object { diff --git a/lib/manager/pipenv/artifacts.spec.ts b/lib/manager/pipenv/artifacts.spec.ts index c4733af3b8c744..2993afd5476b89 100644 --- a/lib/manager/pipenv/artifacts.spec.ts +++ b/lib/manager/pipenv/artifacts.spec.ts @@ -3,11 +3,11 @@ import _fs from 'fs-extra'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { git, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as docker from '../../util/exec/docker'; import * as _env from '../../util/exec/env'; -import type { StatusResult } from '../../util/git'; +import type { StatusResult } from '../../util/git/types'; import type { UpdateArtifactsConfig } from '../types'; import * as pipenv from './artifacts'; @@ -42,7 +42,7 @@ describe('manager/pipenv/artifacts', () => { LC_ALL: 'en_US', }); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); pipFileLock = { _meta: { requires: {} }, @@ -108,7 +108,7 @@ describe('manager/pipenv/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('supports docker mode', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); pipFileLock._meta.requires.python_version = '3.7'; fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any); const execSnapshots = mockExecAll(exec); @@ -131,7 +131,6 @@ describe('manager/pipenv/artifacts', () => { fs.outputFile.mockImplementationOnce(() => { throw new Error('not found'); }); - // FIXME: explicit assert condition expect( await pipenv.updateArtifacts({ packageFileName: 'Pipfile', @@ -139,7 +138,9 @@ describe('manager/pipenv/artifacts', () => { newPackageFileContent: '{}', config, }) - ).toMatchSnapshot(); + ).toEqual([ + { artifactError: { lockFile: 'Pipfile.lock', stderr: 'not found' } }, + ]); }); it('returns updated Pipenv.lock when doing lockfile maintenance', async () => { fs.readFile.mockResolvedValueOnce('Current Pipfile.lock' as any); @@ -159,7 +160,7 @@ describe('manager/pipenv/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('uses pipenv version from Pipfile', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); pipFileLock.default.pipenv.version = '==2020.8.13'; fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any); const execSnapshots = mockExecAll(exec); @@ -178,7 +179,7 @@ describe('manager/pipenv/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('uses pipenv version from Pipfile dev packages', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); pipFileLock.develop.pipenv.version = '==2020.8.13'; fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any); const execSnapshots = mockExecAll(exec); @@ -197,7 +198,7 @@ describe('manager/pipenv/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('uses pipenv version from config', async () => { - setGlobalConfig(dockerAdminConfig); + GlobalConfig.set(dockerAdminConfig); pipFileLock.default.pipenv.version = '==2020.8.13'; fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any); const execSnapshots = mockExecAll(exec); diff --git a/lib/manager/pipenv/artifacts.ts b/lib/manager/pipenv/artifacts.ts index 92b0f371ddf523..84ead7814ad26b 100644 --- a/lib/manager/pipenv/artifacts.ts +++ b/lib/manager/pipenv/artifacts.ts @@ -1,7 +1,8 @@ import { quote } from 'shlex'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { deleteLocalFile, ensureCacheDir, @@ -103,10 +104,8 @@ export async function updateArtifacts({ image: 'python', tagConstraint, tagScheme: 'pep440', - preCommands: [ - `pip install --user ${quote(`pipenv${pipenvConstraint}`)}`, - ], }, + preCommands: [`pip install --user ${quote(`pipenv${pipenvConstraint}`)}`], }; logger.debug({ cmd }, 'pipenv lock command'); await exec(cmd, execOptions); diff --git a/lib/manager/pipenv/extract.spec.ts b/lib/manager/pipenv/extract.spec.ts index 88804599b428df..1e5b711304d6a5 100644 --- a/lib/manager/pipenv/extract.spec.ts +++ b/lib/manager/pipenv/extract.spec.ts @@ -83,24 +83,24 @@ describe('manager/pipenv/extract', () => { '[packages]\r\nfoo = "==1.0.0"\r\n' + '[requires]\r\npython_version = "3.8"'; const res = await extractPackageFile(content, 'Pipfile'); - expect(res.constraints.python).toEqual('== 3.8.*'); + expect(res.constraints.python).toBe('== 3.8.*'); }); it('gets python constraint from python_full_version', async () => { const content = '[packages]\r\nfoo = "==1.0.0"\r\n' + '[requires]\r\npython_full_version = "3.8.6"'; const res = await extractPackageFile(content, 'Pipfile'); - expect(res.constraints.python).toEqual('== 3.8.6'); + expect(res.constraints.python).toBe('== 3.8.6'); }); it('gets pipenv constraint from packages', async () => { const content = '[packages]\r\npipenv = "==2020.8.13"'; const res = await extractPackageFile(content, 'Pipfile'); - expect(res.constraints.pipenv).toEqual('==2020.8.13'); + expect(res.constraints.pipenv).toBe('==2020.8.13'); }); it('gets pipenv constraint from dev-packages', async () => { const content = '[dev-packages]\r\npipenv = "==2020.8.13"'; const res = await extractPackageFile(content, 'Pipfile'); - expect(res.constraints.pipenv).toEqual('==2020.8.13'); + expect(res.constraints.pipenv).toBe('==2020.8.13'); }); }); }); diff --git a/lib/manager/poetry/__snapshots__/extract.spec.ts.snap b/lib/manager/poetry/__snapshots__/extract.spec.ts.snap index 5b08fdd3019b50..0cef52a3009249 100644 --- a/lib/manager/poetry/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/poetry/__snapshots__/extract.spec.ts.snap @@ -1,12 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/poetry/extract extractPackageFile() dedupes registries 1`] = ` -Array [ - "https://pypi.org/pypi/", - "https://bar.baz/+simple/", -] -`; - exports[`manager/poetry/extract extractPackageFile() extracts mixed versioning types 1`] = ` Object { "constraints": Object {}, diff --git a/lib/manager/poetry/artifacts.spec.ts b/lib/manager/poetry/artifacts.spec.ts index 357878b605be62..02440b20e92d19 100644 --- a/lib/manager/poetry/artifacts.spec.ts +++ b/lib/manager/poetry/artifacts.spec.ts @@ -3,7 +3,7 @@ import _fs from 'fs-extra'; import { join } from 'upath'; import { envMock, mockExecAll } from '../../../test/exec-util'; import { loadFixture, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import * as _datasource from '../../datasource'; import * as docker from '../../util/exec/docker'; @@ -36,7 +36,7 @@ describe('manager/poetry/artifacts', () => { beforeEach(() => { jest.resetAllMocks(); env.getChildProcessEnv.mockReturnValue(envMock.basic); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); it('returns null if no poetry.lock found', async () => { @@ -130,7 +130,7 @@ describe('manager/poetry/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('returns updated poetry.lock using docker', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('[metadata]\n' as any); const execSnapshots = mockExecAll(exec); fs.readFile.mockReturnValueOnce('New poetry.lock' as any); @@ -155,7 +155,7 @@ describe('manager/poetry/artifacts', () => { expect(execSnapshots).toMatchSnapshot(); }); it('returns updated poetry.lock using docker (constraints)', async () => { - setGlobalConfig({ ...adminConfig, binarySource: 'docker' }); + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce( '[metadata]\npython-versions = "~2.7 || ^3.4"' as any ); @@ -184,7 +184,6 @@ describe('manager/poetry/artifacts', () => { throw new Error('not found'); }); const updatedDeps = [{ depName: 'dep1' }]; - // FIXME: explicit assert condition expect( await updateArtifacts({ packageFileName: 'pyproject.toml', @@ -192,7 +191,7 @@ describe('manager/poetry/artifacts', () => { newPackageFileContent: '{}', config, }) - ).toMatchSnapshot(); + ).toMatchSnapshot([{ artifactError: { lockFile: 'poetry.lock' } }]); }); it('returns updated poetry.lock when doing lockfile maintenance', async () => { fs.readFile.mockResolvedValueOnce('Old poetry.lock' as any); diff --git a/lib/manager/poetry/artifacts.ts b/lib/manager/poetry/artifacts.ts index fc541b4e3109fc..c6817201f48263 100644 --- a/lib/manager/poetry/artifacts.ts +++ b/lib/manager/poetry/artifacts.ts @@ -3,7 +3,8 @@ import is from '@sindresorhus/is'; import { quote } from 'shlex'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { ExecOptions, exec } from '../../util/exec'; +import { exec } from '../../util/exec'; +import type { ExecOptions } from '../../util/exec/types'; import { deleteLocalFile, getSiblingFileName, @@ -139,8 +140,8 @@ export async function updateArtifacts({ image: 'python', tagConstraint, tagScheme: 'poetry', - preCommands: [poetryInstall], }, + preCommands: [poetryInstall], }; await exec(cmd, execOptions); const newPoetryLockContent = await readLocalFile(lockFileName, 'utf8'); diff --git a/lib/manager/poetry/extract.spec.ts b/lib/manager/poetry/extract.spec.ts index 2b8981d29b91ce..066f1c10e22884 100644 --- a/lib/manager/poetry/extract.spec.ts +++ b/lib/manager/poetry/extract.spec.ts @@ -73,19 +73,58 @@ describe('manager/poetry/extract', () => { }); it('dedupes registries', async () => { const res = await extractPackageFile(pyproject8toml, filename); - // FIXME: explicit assert condition - expect(res.registryUrls).toMatchSnapshot(); + expect(res).toMatchObject({ + registryUrls: ['https://pypi.org/pypi/', 'https://bar.baz/+simple/'], + }); }); it('extracts mixed versioning types', async () => { const res = await extractPackageFile(pyproject9toml, filename); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + deps: [ + { depName: 'dep1', currentValue: '0.2' }, + { depName: 'dep2', currentValue: '1.1.0' }, + { depName: 'dep3', currentValue: '1.0a1' }, + { depName: 'dep4', currentValue: '1.0b2' }, + { depName: 'dep5', currentValue: '1.0rc1' }, + { depName: 'dep6', currentValue: '1.0.dev4' }, + { depName: 'dep7', currentValue: '1.0c1' }, + { depName: 'dep8', currentValue: '2012.2' }, + { depName: 'dep9', currentValue: '1.0.dev456' }, + { depName: 'dep10', currentValue: '1.0a1' }, + { depName: 'dep11', currentValue: '1.0a2.dev456' }, + { depName: 'dep12', currentValue: '1.0a12.dev456' }, + { depName: 'dep13', currentValue: '1.0a12' }, + { depName: 'dep14', currentValue: '1.0b1.dev456' }, + { depName: 'dep15', currentValue: '1.0b2' }, + { depName: 'dep16', currentValue: '1.0b2.post345.dev456' }, + { depName: 'dep17', currentValue: '1.0b2.post345' }, + { depName: 'dep18', currentValue: '1.0rc1.dev456' }, + { depName: 'dep19', currentValue: '1.0rc1' }, + { depName: 'dep20', currentValue: '1.0' }, + { depName: 'dep21', currentValue: '1.0+abc.5' }, + { depName: 'dep22', currentValue: '1.0+abc.7' }, + { depName: 'dep23', currentValue: '1.0+5' }, + { depName: 'dep24', currentValue: '1.0.post456.dev34' }, + { depName: 'dep25', currentValue: '1.0.post456' }, + { depName: 'dep26', currentValue: '1.1.dev1' }, + { depName: 'dep27', currentValue: '~=3.1' }, + { depName: 'dep28', currentValue: '~=3.1.2' }, + { depName: 'dep29', currentValue: '~=3.1a1' }, + { depName: 'dep30', currentValue: '==3.1' }, + { depName: 'dep31', currentValue: '==3.1.*' }, + { depName: 'dep32', currentValue: '~=3.1.0, !=3.1.3' }, + { depName: 'dep33', currentValue: '<=2.0' }, + { depName: 'dep34', currentValue: '<2.0' }, + ], + }); }); it('resolves lockedVersions from the lockfile', async () => { fs.readLocalFile.mockResolvedValue(pyproject11tomlLock); const res = await extractPackageFile(pyproject11toml, filename); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + constraints: { python: '^3.9' }, + deps: [{ lockedVersion: '1.17.5' }], + }); }); it('skips git dependencies', async () => { const content = diff --git a/lib/manager/range.spec.ts b/lib/manager/range.spec.ts index 4726cc910e5999..943901f89a5018 100644 --- a/lib/manager/range.spec.ts +++ b/lib/manager/range.spec.ts @@ -7,7 +7,7 @@ describe('manager/range', () => { manager: 'npm', rangeStrategy: 'widen', }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('returns manager strategy', () => { const config: RangeConfig = { @@ -16,20 +16,20 @@ describe('manager/range', () => { depType: 'dependencies', packageJsonType: 'app', }; - expect(getRangeStrategy(config)).toEqual('pin'); + expect(getRangeStrategy(config)).toBe('pin'); }); it('defaults to replace', () => { const config: RangeConfig = { manager: 'circleci', rangeStrategy: 'auto', }; - expect(getRangeStrategy(config)).toEqual('replace'); + expect(getRangeStrategy(config)).toBe('replace'); }); it('returns rangeStrategy if not auto', () => { const config: RangeConfig = { manager: 'circleci', rangeStrategy: 'future', }; - expect(getRangeStrategy(config)).toEqual('future'); + expect(getRangeStrategy(config)).toBe('future'); }); }); diff --git a/lib/manager/regex/index.spec.ts b/lib/manager/regex/index.spec.ts index 62e3fdc024a241..480af832794e11 100644 --- a/lib/manager/regex/index.spec.ts +++ b/lib/manager/regex/index.spec.ts @@ -30,10 +30,10 @@ describe('manager/regex/index', () => { ); expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(8); - expect(res.deps.find((dep) => dep.depName === 'yarn').versioning).toEqual( + expect(res.deps.find((dep) => dep.depName === 'yarn').versioning).toBe( 'semver' ); - expect(res.deps.find((dep) => dep.depName === 'gradle').versioning).toEqual( + expect(res.deps.find((dep) => dep.depName === 'gradle').versioning).toBe( 'maven' ); expect(res.deps.filter((dep) => dep.depType === 'final')).toHaveLength(8); @@ -80,7 +80,7 @@ describe('manager/regex/index', () => { res.deps.find( (dep) => dep.depName === 'openresty/headers-more-nginx-module' ).extractVersion - ).toEqual('^v(?.*)$'); + ).toBe('^v(?.*)$'); }); it('extracts registryUrl', async () => { const config = { @@ -165,8 +165,8 @@ describe('manager/regex/index', () => { expect(res.deps).toHaveLength(2); expect( res.deps.find((dep) => dep.depName === 'nodejs/node').versioning - ).toEqual('node'); - expect(res.deps.find((dep) => dep.depName === 'gradle').versioning).toEqual( + ).toBe('node'); + expect(res.deps.find((dep) => dep.depName === 'gradle').versioning).toBe( 'maven' ); }); @@ -225,7 +225,7 @@ describe('manager/regex/index', () => { config ); expect(res.deps).toHaveLength(1); - expect(res.deps[0].depName).toEqual('docker.io/prom/prometheus'); + expect(res.deps[0].depName).toBe('docker.io/prom/prometheus'); expect(res).toMatchSnapshot(); }); diff --git a/lib/manager/regex/index.ts b/lib/manager/regex/index.ts index 7557012ad67dc5..d07df03edc28d1 100644 --- a/lib/manager/regex/index.ts +++ b/lib/manager/regex/index.ts @@ -11,6 +11,167 @@ export const defaultConfig = { pinDigests: false, }; +const validMatchFields = [ + 'depName', + 'lookupName', + 'currentValue', + 'currentDigest', + 'datasource', + 'versioning', + 'extractVersion', + 'registryUrl', + 'depType', +]; + +function regexMatchAll(regex: RegExp, content: string): RegExpMatchArray[] { + const matches: RegExpMatchArray[] = []; + let matchResult; + do { + matchResult = regex.exec(content); + if (matchResult) { + matches.push(matchResult); + } + } while (matchResult); + return matches; +} + +function createDependency( + extractionTemplate: ExtractionTemplate, + config: CustomExtractConfig, + dep?: PackageDependency +): PackageDependency { + const dependency = dep || {}; + const { groups, replaceString } = extractionTemplate; + + function updateDependency(field: string, value: string): void { + switch (field) { + case 'registryUrl': + // check if URL is valid and pack inside an array + try { + const url = new URL(value).toString(); + dependency.registryUrls = [url]; + } catch (err) { + logger.warn({ value }, 'Invalid regex manager registryUrl'); + } + break; + default: + dependency[field] = value; + break; + } + } + + for (const field of validMatchFields) { + const fieldTemplate = `${field}Template`; + if (config[fieldTemplate]) { + try { + const compiled = template.compile(config[fieldTemplate], groups, false); + updateDependency(field, compiled); + } catch (err) { + logger.warn( + { template: config[fieldTemplate] }, + 'Error compiling template for custom manager' + ); + return null; + } + } else if (groups[field]) { + updateDependency(field, groups[field]); + } + } + dependency.replaceString = replaceString; + return dependency; +} + +function handleAny( + content: string, + packageFile: string, + config: CustomExtractConfig +): PackageDependency[] { + return config.matchStrings + .map((matchString) => regEx(matchString, 'g')) + .flatMap((regex) => regexMatchAll(regex, content)) // match all regex to content, get all matches, reduce to single array + .map((matchResult) => + createDependency( + { groups: matchResult.groups, replaceString: matchResult[0] }, + config + ) + ); +} + +function mergeGroups( + mergedGroup: Record, + secondGroup: Record +): Record { + return { ...mergedGroup, ...secondGroup }; +} + +export function mergeExtractionTemplate( + base: ExtractionTemplate, + addition: ExtractionTemplate +): ExtractionTemplate { + return { + groups: mergeGroups(base.groups, addition.groups), + replaceString: addition.replaceString ?? base.replaceString, + }; +} + +function handleCombination( + content: string, + packageFile: string, + config: CustomExtractConfig +): PackageDependency[] { + const matches = config.matchStrings + .map((matchString) => regEx(matchString, 'g')) + .flatMap((regex) => regexMatchAll(regex, content)); // match all regex to content, get all matches, reduce to single array + + if (!matches.length) { + return []; + } + + const extraction = matches + .map((match) => ({ + groups: match.groups, + replaceString: match?.groups?.currentValue ? match[0] : undefined, + })) + .reduce((base, addition) => mergeExtractionTemplate(base, addition)); + return [createDependency(extraction, config)]; +} + +function handleRecursive( + content: string, + packageFile: string, + config: CustomExtractConfig, + index = 0, + combinedGroups: Record = {} +): PackageDependency[] { + const regexes = config.matchStrings.map((matchString) => + regEx(matchString, 'g') + ); + // abort if we have no matchString anymore + if (!regexes[index]) { + return []; + } + return regexMatchAll(regexes[index], content).flatMap((match) => { + // if we have a depName and a currentValue which have the minimal viable definition + if (match?.groups?.depName && match?.groups?.currentValue) { + return createDependency( + { + groups: mergeGroups(combinedGroups, match.groups), + replaceString: match[0], + }, + config + ); + } + + return handleRecursive( + match[0], + packageFile, + config, + index + 1, + mergeGroups(combinedGroups, match.groups || {}) + ); + }); +} + export function extractPackageFile( content: string, packageFile: string, diff --git a/lib/manager/setup-cfg/range.spec.ts b/lib/manager/setup-cfg/range.spec.ts index 5f6cc8796412cf..dd7e15f55e9a2d 100644 --- a/lib/manager/setup-cfg/range.spec.ts +++ b/lib/manager/setup-cfg/range.spec.ts @@ -4,10 +4,10 @@ import { getRangeStrategy } from '.'; describe('manager/setup-cfg/range', () => { it('returns same if not auto', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; - expect(getRangeStrategy(config)).toEqual('widen'); + expect(getRangeStrategy(config)).toBe('widen'); }); it('replaces if auto', () => { const config: RangeConfig = { rangeStrategy: 'auto' }; - expect(getRangeStrategy(config)).toEqual('replace'); + expect(getRangeStrategy(config)).toBe('replace'); }); }); diff --git a/lib/manager/swift/extract.ts b/lib/manager/swift/extract.ts index cff2eceb9cf2b9..14476c5f559d71 100644 --- a/lib/manager/swift/extract.ts +++ b/lib/manager/swift/extract.ts @@ -166,7 +166,7 @@ export function extractPackageFile( while (match) { const { idx, len, label, substr } = match; - // eslint-disable-next-line default-case + switch (state) { case null: if (deps.length) { diff --git a/lib/manager/swift/index.spec.ts b/lib/manager/swift/index.spec.ts index c5dba98d7f9ed2..ca395bceb01727 100644 --- a/lib/manager/swift/index.spec.ts +++ b/lib/manager/swift/index.spec.ts @@ -97,45 +97,38 @@ describe('manager/swift/index', () => { ).not.toBeNull(); }); it('parses package descriptions', () => { - // FIXME: explicit assert condition expect( extractPackageFile( `dependencies:[.package(url:"https://github.com/vapor/vapor.git",from:"1.2.3")]` ) - ).toMatchSnapshot(); - // FIXME: explicit assert condition + ).toMatchSnapshot({ deps: [{ currentValue: 'from:"1.2.3"' }] }); expect( extractPackageFile( `dependencies:[.package(url:"https://github.com/vapor/vapor.git","1.2.3"...)]` ) - ).toMatchSnapshot(); - // FIXME: explicit assert condition + ).toMatchSnapshot({ deps: [{ currentValue: '"1.2.3"...' }] }); expect( extractPackageFile( `dependencies:[.package(url:"https://github.com/vapor/vapor.git","1.2.3"..."1.2.4")]` ) - ).toMatchSnapshot(); - // FIXME: explicit assert condition + ).toMatchSnapshot({ deps: [{ currentValue: '"1.2.3"..."1.2.4"' }] }); expect( extractPackageFile( `dependencies:[.package(url:"https://github.com/vapor/vapor.git","1.2.3"..<"1.2.4")]` ) - ).toMatchSnapshot(); - // FIXME: explicit assert condition + ).toMatchSnapshot({ deps: [{ currentValue: '"1.2.3"..<"1.2.4"' }] }); expect( extractPackageFile( `dependencies:[.package(url:"https://github.com/vapor/vapor.git",..."1.2.3")]` ) - ).toMatchSnapshot(); - // FIXME: explicit assert condition + ).toMatchSnapshot({ deps: [{ currentValue: '..."1.2.3"' }] }); expect( extractPackageFile( `dependencies:[.package(url:"https://github.com/vapor/vapor.git",..<"1.2.3")]` ) - ).toMatchSnapshot(); + ).toMatchSnapshot({ deps: [{ currentValue: '..<"1.2.3"' }] }); }); it('parses multiple packages', () => { - // FIXME: explicit assert condition expect(extractPackageFile(pkgContent)).toMatchSnapshot(); }); }); diff --git a/lib/manager/terraform/__fixtures__/1.tf b/lib/manager/terraform/__fixtures__/1.tf index 5bb3886b382a36..6199b622cda0d1 100644 --- a/lib/manager/terraform/__fixtures__/1.tf +++ b/lib/manager/terraform/__fixtures__/1.tf @@ -92,7 +92,7 @@ module "addons_aws" { aws-ebs-csi-driver = { enabled = true is_default_class = true - version = "1.0.0" + version = "1.0.0" } @@ -143,7 +143,7 @@ provider "gitlab" { } provider "gitlab" { - token = "${var.gitlab_token}" + token = "${var.gitlab_token}" version = "=1.3" } @@ -189,6 +189,26 @@ module "gittags_ssh" { source = "git::ssh://git@bitbucket.com/hashicorp/example?ref=v1.0.3" } +module "bitbucket_ssh" { + source = "git::ssh://git@bitbucket.org/hashicorp/example.git?ref=v1.0.0" +} + +module "bitbucket_https" { + source = "git::https://git@bitbucket.org/hashicorp/example.git?ref=v1.0.0" +} + +module "bitbucket_plain" { + source = "bitbucket.org/hashicorp/example.git?ref=v1.0.0" +} + +module "bitbucket_subfolder" { + source = "bitbucket.org/hashicorp/example.git/terraform?ref=v1.0.0" +} + +module "bitbucket_subfolder_with_double_slash" { + source = "bitbucket.org/hashicorp/example.git//terraform?ref=v1.0.0" +} + terraform { required_providers { aws = ">= 2.7.0" diff --git a/lib/manager/terraform/__snapshots__/extract.spec.ts.snap b/lib/manager/terraform/__snapshots__/extract.spec.ts.snap index fa6022ae042664..af0195c598ad9d 100644 --- a/lib/manager/terraform/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/terraform/__snapshots__/extract.spec.ts.snap @@ -212,6 +212,7 @@ Object { "depType": "provider", "lockedVersion": undefined, "lookupName": "hashicorp/helm", + "skipReason": "no-version", }, Object { "currentValue": "V1.9", @@ -263,6 +264,41 @@ Object { "depType": "module", "lookupName": "ssh://git@bitbucket.com/hashicorp/example", }, + Object { + "currentValue": "v1.0.0", + "datasource": "bitbucket-tags", + "depName": "hashicorp/example", + "depType": "module", + "lookupName": "hashicorp/example", + }, + Object { + "currentValue": "v1.0.0", + "datasource": "bitbucket-tags", + "depName": "hashicorp/example", + "depType": "module", + "lookupName": "hashicorp/example", + }, + Object { + "currentValue": "v1.0.0", + "datasource": "bitbucket-tags", + "depName": "hashicorp/example", + "depType": "module", + "lookupName": "hashicorp/example", + }, + Object { + "currentValue": "v1.0.0", + "datasource": "bitbucket-tags", + "depName": "hashicorp/example", + "depType": "module", + "lookupName": "hashicorp/example", + }, + Object { + "currentValue": "v1.0.0", + "datasource": "bitbucket-tags", + "depName": "hashicorp/example", + "depType": "module", + "lookupName": "hashicorp/example", + }, Object { "currentValue": ">= 2.7.0", "datasource": "terraform-provider", @@ -285,7 +321,7 @@ Object { "depName": "hashicorp/terraform", "depType": "required_version", "extractVersion": "v(?.*)$", - "lineNumber": 230, + "lineNumber": 250, }, Object { "currentValue": "2.7.2", diff --git a/lib/manager/terraform/common.ts b/lib/manager/terraform/common.ts index 25be93cd16927e..78cfaec994fbaf 100644 --- a/lib/manager/terraform/common.ts +++ b/lib/manager/terraform/common.ts @@ -1,3 +1,6 @@ +// FIXME #12556 +/* eslint-disable @typescript-eslint/naming-convention */ + export enum TerraformDependencyTypes { unknown = 'unknown', module = 'module', diff --git a/lib/manager/terraform/extract.spec.ts b/lib/manager/terraform/extract.spec.ts index 109092ffc35426..745489c41a4eb4 100644 --- a/lib/manager/terraform/extract.spec.ts +++ b/lib/manager/terraform/extract.spec.ts @@ -1,6 +1,6 @@ import { join } from 'upath'; import { fs, loadFixture } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { extractPackageFile } from '.'; @@ -25,7 +25,7 @@ jest.mock('../../util/fs'); describe('manager/terraform/extract', () => { beforeEach(() => { - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); describe('extractPackageFile()', () => { it('returns null for empty', async () => { @@ -35,8 +35,8 @@ describe('manager/terraform/extract', () => { it('extracts', async () => { const res = await extractPackageFile(tf1, '1.tf', {}); expect(res).toMatchSnapshot(); - expect(res.deps).toHaveLength(46); - expect(res.deps.filter((dep) => dep.skipReason)).toHaveLength(8); + expect(res.deps).toHaveLength(51); + expect(res.deps.filter((dep) => dep.skipReason)).toHaveLength(9); }); it('returns null if only local deps', async () => { diff --git a/lib/manager/terraform/extract.ts b/lib/manager/terraform/extract.ts index cdca8db112ef24..3034ab895884b1 100644 --- a/lib/manager/terraform/extract.ts +++ b/lib/manager/terraform/extract.ts @@ -145,7 +145,7 @@ export async function extractPackageFile( /* istanbul ignore next */ default: } - // eslint-disable-next-line no-param-reassign + delete dep.managerData; }); if (deps.some((dep) => dep.skipReason !== 'local')) { diff --git a/lib/manager/terraform/lockfile/hash.spec.ts b/lib/manager/terraform/lockfile/hash.spec.ts index d14da9c54cfb9f..b2b06757525d36 100644 --- a/lib/manager/terraform/lockfile/hash.spec.ts +++ b/lib/manager/terraform/lockfile/hash.spec.ts @@ -2,7 +2,7 @@ import { createReadStream } from 'fs'; import { DirectoryResult, dir } from 'tmp-promise'; import * as httpMock from '../../../../test/http-mock'; import { getFixturePath, loadFixture, logger } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { TerraformProviderDatasource } from '../../../datasource/terraform-provider'; import { Logger } from '../../../logger/types'; import { TerraformProviderHash } from './hash'; @@ -17,7 +17,7 @@ describe('manager/terraform/lockfile/hash', () => { beforeEach(async () => { cacheDir = await dir({ unsafeCleanup: true }); - setGlobalConfig({ cacheDir: cacheDir.path }); + GlobalConfig.set({ cacheDir: cacheDir.path }); }); afterEach(() => cacheDir.cleanup()); diff --git a/lib/manager/terraform/lockfile/hash.ts b/lib/manager/terraform/lockfile/hash.ts index 94799f362c3d59..f20b2ec0c450d2 100644 --- a/lib/manager/terraform/lockfile/hash.ts +++ b/lib/manager/terraform/lockfile/hash.ts @@ -9,6 +9,7 @@ import { cache } from '../../../util/cache/package/decorator'; import * as fs from '../../../util/fs'; import { ensureCacheDir } from '../../../util/fs'; import { Http } from '../../../util/http'; +import { regEx } from '../../../util/regex'; export class TerraformProviderHash { static http = new Http(TerraformProviderDatasource.id); @@ -31,7 +32,7 @@ export class TerraformProviderHash { // add double space, the filename and a new line char rootHash.update(' '); - const fileName = file.replace(/^.*[\\/]/, ''); // TODO #12070 + const fileName = file.replace(regEx(/^.*[\\/]/), ''); // TODO #12071 rootHash.update(fileName); rootHash.update('\n'); } diff --git a/lib/manager/terraform/lockfile/index.spec.ts b/lib/manager/terraform/lockfile/index.spec.ts index 2964b081bd0fce..0c2a4d52c1e779 100644 --- a/lib/manager/terraform/lockfile/index.spec.ts +++ b/lib/manager/terraform/lockfile/index.spec.ts @@ -1,6 +1,6 @@ import { join } from 'upath'; import { fs, loadFixture, mocked } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { getPkgReleases } from '../../../datasource'; import type { UpdateArtifactsConfig } from '../../types'; import { TerraformProviderHash } from './hash'; @@ -33,7 +33,7 @@ describe('manager/terraform/lockfile/index', () => { beforeEach(() => { jest.resetAllMocks(); jest.resetModules(); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); }); it('returns null if no .terraform.lock.hcl found', async () => { diff --git a/lib/manager/terraform/lockfile/index.ts b/lib/manager/terraform/lockfile/index.ts index 4fe381265a7f11..c1a34a6d4ad5fe 100644 --- a/lib/manager/terraform/lockfile/index.ts +++ b/lib/manager/terraform/lockfile/index.ts @@ -110,10 +110,7 @@ export async function updateArtifacts({ } } // if no updates have been found or there are failed hashes abort - if ( - updates.length === 0 || - updates.some((value) => value.newHashes == null) - ) { + if (updates.length === 0 || updates.some((value) => !value.newHashes)) { return null; } diff --git a/lib/manager/terraform/lockfile/util.ts b/lib/manager/terraform/lockfile/util.ts index d6bedc5d2c367b..4e257557c32270 100644 --- a/lib/manager/terraform/lockfile/util.ts +++ b/lib/manager/terraform/lockfile/util.ts @@ -1,4 +1,5 @@ import { getSiblingFileName, readLocalFile } from '../../../util/fs'; +import { regEx } from '../../../util/regex'; import { get as getVersioning } from '../../../versioning'; import type { UpdateArtifactsResult } from '../../types'; import type { @@ -8,13 +9,16 @@ import type { ProviderSlice, } from './types'; -const providerStartLineRegex = - /^provider "(?[^/]*)\/(?[^/]*)\/(?[^/]*)"/; // TODO #12070 -const versionLineRegex = - /^(?[\s]*version[\s]*=[\s]*")(?[^"']+)(?".*)$/; // TODO #12070 -const constraintLineRegex = - /^(?[\s]*constraints[\s]*=[\s]*")(?[^"']+)(?".*)$/; // TODO #12070 -const hashLineRegex = /^(?\s*")(?[^"]+)(?",.*)$/; // TODO #12070 +const providerStartLineRegex = regEx( + `^provider "(?[^/]*)\\/(?[^/]*)\\/(?[^/]*)"` +); +const versionLineRegex = regEx( + `^(?[\\s]*version[\\s]*=[\\s]*")(?[^"']+)(?".*)$` +); +const constraintLineRegex = regEx( + `^(?[\\s]*constraints[\\s]*=[\\s]*")(?[^"']+)(?".*)$` +); +const hashLineRegex = regEx(`^(?\\s*")(?[^"]+)(?",.*)$`); const lockFile = '.terraform.lock.hcl'; diff --git a/lib/manager/terraform/modules.spec.ts b/lib/manager/terraform/modules.spec.ts index f7c1674e5a655b..fcbc3fc33333b1 100644 --- a/lib/manager/terraform/modules.spec.ts +++ b/lib/manager/terraform/modules.spec.ts @@ -1,4 +1,8 @@ -import { gitTagsRefMatchRegex, githubRefMatchRegex } from './modules'; +import { + bitbucketRefMatchRegex, + gitTagsRefMatchRegex, + githubRefMatchRegex, +} from './modules'; describe('manager/terraform/modules', () => { describe('githubRefMatchRegex', () => { @@ -60,4 +64,53 @@ describe('manager/terraform/modules', () => { expect(ssh.tag).toBe('v1.0.0'); }); }); + describe('bitbucketRefMatchRegex', () => { + it('should split workspace, project and tag from source', () => { + const ssh = bitbucketRefMatchRegex.exec( + 'git::ssh://git@bitbucket.org/hashicorp/example.git?ref=v1.0.0' + ).groups; + const https = bitbucketRefMatchRegex.exec( + 'git::https://git@bitbucket.org/hashicorp/example.git?ref=v1.0.0' + ).groups; + const plain = bitbucketRefMatchRegex.exec( + 'bitbucket.org/hashicorp/example.git?ref=v1.0.0' + ).groups; + const subfolder = bitbucketRefMatchRegex.exec( + 'bitbucket.org/hashicorp/example.git/terraform?ref=v1.0.0' + ).groups; + const subfolderWithDoubleSlash = bitbucketRefMatchRegex.exec( + 'bitbucket.org/hashicorp/example.git//terraform?ref=v1.0.0' + ).groups; + + expect(ssh.workspace).toBe('hashicorp'); + expect(ssh.project).toBe('example'); + expect(ssh.tag).toBe('v1.0.0'); + + expect(https.workspace).toBe('hashicorp'); + expect(https.project).toBe('example'); + expect(https.tag).toBe('v1.0.0'); + + expect(plain.workspace).toBe('hashicorp'); + expect(plain.project).toBe('example'); + expect(plain.tag).toBe('v1.0.0'); + + expect(subfolder.workspace).toBe('hashicorp'); + expect(subfolder.project).toBe('example'); + expect(subfolder.tag).toBe('v1.0.0'); + + expect(subfolderWithDoubleSlash.workspace).toBe('hashicorp'); + expect(subfolderWithDoubleSlash.project).toBe('example'); + expect(subfolderWithDoubleSlash.tag).toBe('v1.0.0'); + }); + + it('should parse alpha-numeric characters as well as dots, underscores, and dashes in repo names', () => { + const dots = bitbucketRefMatchRegex.exec( + 'bitbucket.org/hashicorp/example.repo-123.git?ref=v1.0.0' + ).groups; + + expect(dots.workspace).toBe('hashicorp'); + expect(dots.project).toBe('example.repo-123'); + expect(dots.tag).toBe('v1.0.0'); + }); + }); }); diff --git a/lib/manager/terraform/modules.ts b/lib/manager/terraform/modules.ts index ae24e68fda38ae..594838baf11c3d 100644 --- a/lib/manager/terraform/modules.ts +++ b/lib/manager/terraform/modules.ts @@ -1,3 +1,4 @@ +import { BitBucketTagsDatasource } from '../../datasource/bitbucket-tags'; import { GitTagsDatasource } from '../../datasource/git-tags'; import * as datasourceGithubTags from '../../datasource/github-tags'; import { TerraformModuleDatasource } from '../../datasource/terraform-module'; @@ -12,6 +13,9 @@ import type { ExtractionResult } from './types'; export const githubRefMatchRegex = regEx( /github\.com([/:])(?[^/]+\/[a-z0-9-_.]+).*\?ref=(?.*)$/i ); +export const bitbucketRefMatchRegex = regEx( + /(?:git::)?(?(?:http|https|ssh)?(?::\/\/)?(?:.*@)?(?bitbucket\.org\/(?.*)\/(?.*).git\/?(?.*)))\?ref=(?.*)$/ +); export const gitTagsRefMatchRegex = regEx( /(?:git::)?(?(?:http|https|ssh):\/\/(?:.*@)?(?.*.*\/(?.*\/.*)))\?ref=(?.*)$/ ); @@ -24,7 +28,6 @@ export function extractTerraformModule( ): ExtractionResult { const result = extractTerraformProvider(startingLine, lines, moduleName); result.dependencies.forEach((dep) => { - // eslint-disable-next-line no-param-reassign dep.managerData.terraformDependencyType = TerraformDependencyTypes.module; }); return result; @@ -32,14 +35,24 @@ export function extractTerraformModule( export function analyseTerraformModule(dep: PackageDependency): void { const githubRefMatch = githubRefMatchRegex.exec(dep.managerData.source); + const bitbucketRefMatch = bitbucketRefMatchRegex.exec(dep.managerData.source); const gitTagsRefMatch = gitTagsRefMatchRegex.exec(dep.managerData.source); - /* eslint-disable no-param-reassign */ + if (githubRefMatch) { dep.lookupName = githubRefMatch.groups.project.replace(regEx(/\.git$/), ''); dep.depType = 'module'; dep.depName = 'github.com/' + dep.lookupName; dep.currentValue = githubRefMatch.groups.tag; dep.datasource = datasourceGithubTags.id; + } else if (bitbucketRefMatch) { + dep.depType = 'module'; + dep.depName = + bitbucketRefMatch.groups.workspace + + '/' + + bitbucketRefMatch.groups.project; + dep.lookupName = dep.depName; + dep.currentValue = bitbucketRefMatch.groups.tag; + dep.datasource = BitBucketTagsDatasource.id; } else if (gitTagsRefMatch) { dep.depType = 'module'; if (gitTagsRefMatch.groups.path.includes('//')) { @@ -70,5 +83,4 @@ export function analyseTerraformModule(dep: PackageDependency): void { logger.debug({ dep }, 'terraform dep has no source'); dep.skipReason = SkipReason.NoSource; } - /* eslint-enable no-param-reassign */ } diff --git a/lib/manager/terraform/providers.ts b/lib/manager/terraform/providers.ts index b0d0a37b7d5191..e742b8837cbe35 100644 --- a/lib/manager/terraform/providers.ts +++ b/lib/manager/terraform/providers.ts @@ -42,8 +42,8 @@ export function extractTerraformProvider( // istanbul ignore else if (is.string(line)) { // `{` will be counted wit +1 and `}` with -1. Therefore if we reach braceCounter == 0. We have found the end of the terraform block - const openBrackets = (line.match(/\{/g) || []).length; // TODO #12071 #12070 - const closedBrackets = (line.match(/\}/g) || []).length; // TODO #12071 #12070 + const openBrackets = (line.match(regEx(/\{/g)) || []).length; // TODO #12071 + const closedBrackets = (line.match(regEx(/\}/g)) || []).length; // TODO #12071 braceCounter = braceCounter + openBrackets - closedBrackets; // only update fields inside the root block @@ -75,7 +75,6 @@ export function analyzeTerraformProvider( dep: PackageDependency, locks: ProviderLock[] ): void { - /* eslint-disable no-param-reassign */ dep.depType = 'provider'; dep.depName = dep.managerData.moduleName; dep.datasource = TerraformProviderDatasource.id; @@ -100,5 +99,8 @@ export function analyzeTerraformProvider( massageProviderLookupName(dep); dep.lockedVersion = getLockedVersion(dep, locks); - /* eslint-enable no-param-reassign */ + + if (!dep.currentValue) { + dep.skipReason = SkipReason.NoVersion; + } } diff --git a/lib/manager/terraform/readme.md b/lib/manager/terraform/readme.md index 9d3138c89cb327..50c204b2300a3a 100644 --- a/lib/manager/terraform/readme.md +++ b/lib/manager/terraform/readme.md @@ -22,17 +22,17 @@ Terraform range constraints are supported: - `~> 1.2`: any non-beta version >= 1.2.0 and < 2.0.0, e.g. 1.X.Y - `>= 1.0.0, <= 2.0.0`: any version between 1.0.0 and 2.0.0 inclusive -For fine-grained control, e.g. to turn off only parts of this manager, there are following `depTypes` provided: +For fine-grained control, e.g. to turn off only parts of this manager, you can use the following `depTypes`: -| resource | depType | -| --------------------------- | :---------------: | -| terraform provider | provider | -| required terraform provider | required_provider | -| required terraform version | required_version | -| terraform module | module | -| helm release | helm_release | -| docker container | docker_container | -| docker image | docker_image | -| docker service | docker_service | +| resource | depType | +| --------------------------- | :-----------------: | +| Terraform provider | `provider` | +| required Terraform provider | `required_provider` | +| required Terraform version | `required_version` | +| Terraform module | `module` | +| Helm release | `helm_release` | +| Docker container | `docker_container` | +| Docker image | `docker_image` | +| Docker service | `docker_service` | If you need to change the versioning format, read the [versioning](https://docs.renovatebot.com/modules/versioning/) documentation to learn more. diff --git a/lib/manager/terraform/required-providers.ts b/lib/manager/terraform/required-providers.ts index 6ac73de5120b8c..b59956d46b66d0 100644 --- a/lib/manager/terraform/required-providers.ts +++ b/lib/manager/terraform/required-providers.ts @@ -20,7 +20,6 @@ function extractBlock( line = lines[lineNumber]; const kvMatch = keyValueExtractionRegex.exec(line); if (kvMatch) { - /* eslint-disable no-param-reassign */ switch (kvMatch.groups.key) { case 'source': dep.managerData.source = kvMatch.groups.value; @@ -34,7 +33,6 @@ function extractBlock( default: break; } - /* eslint-enable no-param-reassign */ } } while (line.trim() !== '}'); return lineNumber; @@ -78,8 +76,6 @@ export function analyzeTerraformRequiredProvider( dep: PackageDependency, locks: ProviderLock[] ): void { - /* eslint-disable no-param-reassign */ analyzeTerraformProvider(dep, locks); dep.depType = `required_provider`; - /* eslint-enable no-param-reassign */ } diff --git a/lib/manager/terraform/required-version.ts b/lib/manager/terraform/required-version.ts index dc16f52211336c..12f5ad4bdf0f8e 100644 --- a/lib/manager/terraform/required-version.ts +++ b/lib/manager/terraform/required-version.ts @@ -1,5 +1,6 @@ import * as datasourceGithubTags from '../../datasource/github-tags'; import { logger } from '../../logger'; +import { regEx } from '../../util/regex'; import type { PackageDependency } from '../types'; import { TerraformDependencyTypes } from './common'; import type { ExtractionResult } from './types'; @@ -20,8 +21,8 @@ export function extractTerraformRequiredVersion( const line = lines[lineNumber]; // `{` will be counted wit +1 and `}` with -1. Therefore if we reach braceCounter == 0. We have found the end of the terraform block - const openBrackets = (line.match(/\{/g) || []).length; // TODO #12070 - const closedBrackets = (line.match(/\}/g) || []).length; // TODO #12070 + const openBrackets = (line.match(regEx(/\{/g)) || []).length; + const closedBrackets = (line.match(regEx(/\}/g)) || []).length; braceCounter = braceCounter + openBrackets - closedBrackets; const kvMatch = keyValueExtractionRegex.exec(line); @@ -45,10 +46,8 @@ export function extractTerraformRequiredVersion( } export function analyseTerraformVersion(dep: PackageDependency): void { - /* eslint-disable no-param-reassign */ dep.depType = 'required_version'; dep.datasource = datasourceGithubTags.id; dep.depName = 'hashicorp/terraform'; dep.extractVersion = 'v(?.*)$'; - /* eslint-enable no-param-reassign */ } diff --git a/lib/manager/terraform/resources.ts b/lib/manager/terraform/resources.ts index 075a1b63b34589..46bdf1abc3d0cb 100644 --- a/lib/manager/terraform/resources.ts +++ b/lib/manager/terraform/resources.ts @@ -65,8 +65,6 @@ export function extractTerraformResource( export function analyseTerraformResource( dep: PackageDependency ): void { - /* eslint-disable no-param-reassign */ - switch (dep.managerData.resourceType) { case TerraformResourceTypes.docker_container: if (dep.managerData.image) { @@ -96,7 +94,7 @@ export function analyseTerraformResource( break; case TerraformResourceTypes.helm_release: - if (dep.managerData.chart == null) { + if (!dep.managerData.chart) { dep.skipReason = SkipReason.InvalidName; } else if (checkIfStringIsPath(dep.managerData.chart)) { dep.skipReason = SkipReason.LocalChart; @@ -111,5 +109,4 @@ export function analyseTerraformResource( dep.skipReason = SkipReason.InvalidValue; break; } - /* eslint-enable no-param-reassign */ } diff --git a/lib/manager/terraform/util.ts b/lib/manager/terraform/util.ts index 9793c547c15b22..e0f2f08ad1f9e7 100644 --- a/lib/manager/terraform/util.ts +++ b/lib/manager/terraform/util.ts @@ -50,7 +50,6 @@ export function checkIfStringIsPath(path: string): boolean { } export function massageProviderLookupName(dep: PackageDependency): void { - /* eslint-disable no-param-reassign */ if (!dep.lookupName) { dep.lookupName = dep.depName; } @@ -60,7 +59,6 @@ export function massageProviderLookupName(dep: PackageDependency): void { // handle cases like `Telmate/proxmox` dep.lookupName = dep.lookupName.toLowerCase(); - /* eslint-enable no-param-reassign */ } export function getLockedVersion( diff --git a/lib/manager/terragrunt/common.ts b/lib/manager/terragrunt/common.ts index c5a699e242bc5f..a13e424829e87b 100644 --- a/lib/manager/terragrunt/common.ts +++ b/lib/manager/terragrunt/common.ts @@ -1,3 +1,6 @@ +// FIXME #12556 +/* eslint-disable @typescript-eslint/naming-convention */ + export enum TerragruntResourceTypes { unknown = 'unknown', } diff --git a/lib/manager/terragrunt/extract.ts b/lib/manager/terragrunt/extract.ts index 978359b6a83783..aefc1140683eac 100644 --- a/lib/manager/terragrunt/extract.ts +++ b/lib/manager/terragrunt/extract.ts @@ -61,7 +61,7 @@ export function extractPackageFile(content: string): PackageFile | null { /* istanbul ignore next */ default: } - // eslint-disable-next-line no-param-reassign + delete dep.managerData; }); return { deps }; diff --git a/lib/manager/terragrunt/modules.ts b/lib/manager/terragrunt/modules.ts index f10ba1486ed60b..f7a8f3367e64dc 100644 --- a/lib/manager/terragrunt/modules.ts +++ b/lib/manager/terragrunt/modules.ts @@ -24,7 +24,6 @@ export function extractTerragruntModule( const moduleName = 'terragrunt'; const result = extractTerragruntProvider(startingLine, lines, moduleName); result.dependencies.forEach((dep) => { - // eslint-disable-next-line no-param-reassign dep.managerData.terragruntDependencyType = TerragruntDependencyTypes.terragrunt; }); @@ -34,7 +33,7 @@ export function extractTerragruntModule( export function analyseTerragruntModule(dep: PackageDependency): void { const githubRefMatch = githubRefMatchRegex.exec(dep.managerData.source); const gitTagsRefMatch = gitTagsRefMatchRegex.exec(dep.managerData.source); - /* eslint-disable no-param-reassign */ + if (githubRefMatch) { dep.depType = 'github'; dep.lookupName = githubRefMatch.groups.project.replace(regEx(/\.git$/), ''); @@ -71,5 +70,4 @@ export function analyseTerragruntModule(dep: PackageDependency): void { logger.debug({ dep }, 'terragrunt dep has no source'); dep.skipReason = SkipReason.NoSource; } - /* eslint-enable no-param-reassign */ } diff --git a/lib/manager/terragrunt/readme.md b/lib/manager/terragrunt/readme.md index 00a0d71a653b2c..5c1c9d63a22a6c 100644 --- a/lib/manager/terragrunt/readme.md +++ b/lib/manager/terragrunt/readme.md @@ -4,8 +4,10 @@ You can create a custom [versioning config](/configuration-options/#versioning) For example, if you want to reference a tag like `module-v1.2.5`, a block like this would work: ```json -"terraform": { - "versioning": "regex:^((?.*)-v|v*)(?\\d+)\\.(?\\d+)\\.(?\\d+)$" +{ + "terraform": { + "versioning": "regex:^((?.*)-v|v*)(?\\d+)\\.(?\\d+)\\.(?\\d+)$" + } } ``` diff --git a/lib/manager/travis/__fixtures__/matrix_alias.yml b/lib/manager/travis/__fixtures__/matrix_alias.yml new file mode 100644 index 00000000000000..0ec43788f5c796 --- /dev/null +++ b/lib/manager/travis/__fixtures__/matrix_alias.yml @@ -0,0 +1,5 @@ +matrix: + include: + - env: js-tests + language: node_js + node_js: '11.10.1' diff --git a/lib/manager/travis/__fixtures__/matrix_invalid.yml b/lib/manager/travis/__fixtures__/matrix_invalid.yml new file mode 100644 index 00000000000000..a13a252e2731aa --- /dev/null +++ b/lib/manager/travis/__fixtures__/matrix_invalid.yml @@ -0,0 +1,4 @@ +jobs: + include: + - invalid: '1.0' + diff --git a/lib/manager/travis/__fixtures__/matrix_jobs.yml b/lib/manager/travis/__fixtures__/matrix_jobs.yml new file mode 100644 index 00000000000000..62139533e2b837 --- /dev/null +++ b/lib/manager/travis/__fixtures__/matrix_jobs.yml @@ -0,0 +1,5 @@ +jobs: + include: + - env: js-tests + language: node_js + node_js: '11.10.1' diff --git a/lib/manager/travis/__fixtures__/matrix_jobs_array.yml b/lib/manager/travis/__fixtures__/matrix_jobs_array.yml new file mode 100644 index 00000000000000..c379d5a128d2af --- /dev/null +++ b/lib/manager/travis/__fixtures__/matrix_jobs_array.yml @@ -0,0 +1,5 @@ +jobs: + include: + - env: js-tests + language: node_js + node_js: ['11.10.1', '11.10.2'] diff --git a/lib/manager/travis/__fixtures__/matrix_jobs_array2.yml b/lib/manager/travis/__fixtures__/matrix_jobs_array2.yml new file mode 100644 index 00000000000000..58b70eff6dbc1a --- /dev/null +++ b/lib/manager/travis/__fixtures__/matrix_jobs_array2.yml @@ -0,0 +1,7 @@ +jobs: + include: + - env: js-tests + language: node_js + node_js: + - '11.10.1' + - '11.10.2' diff --git a/lib/manager/travis/extract.spec.ts b/lib/manager/travis/extract.spec.ts index abeb3a98e46a47..1488759e584341 100644 --- a/lib/manager/travis/extract.spec.ts +++ b/lib/manager/travis/extract.spec.ts @@ -1,7 +1,12 @@ import { loadFixture } from '../../../test/util'; -import { extractPackageFile } from './extract'; +import { extractPackageFile } from '.'; const invalidYAML = loadFixture('invalid.yml'); +const matrixYAMLwithNodeSyntaxString = loadFixture('matrix_jobs.yml'); +const matrixYAMLwithNodeSyntaxArray = loadFixture('matrix_jobs_array.yml'); +const matrixYAMLwithNodeSyntaxArray2 = loadFixture('matrix_jobs_array2.yml'); +const matrixYAMLwithNodeSyntaxAlias = loadFixture('matrix_alias.yml'); +const invalidMatrixYAML = loadFixture('matrix_invalid.yml'); describe('manager/travis/extract', () => { describe('extractPackageFile()', () => { @@ -9,14 +14,89 @@ describe('manager/travis/extract', () => { const res = extractPackageFile('blahhhhh:foo:@what\n'); expect(res).toBeNull(); }); + it('returns results', () => { const res = extractPackageFile('node_js:\n - 6\n - 8\n'); expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(2); }); + it('should handle invalid YAML', () => { const res = extractPackageFile(invalidYAML); expect(res).toBeNull(); }); + + it('handles matrix node_js syntax with node_js string', () => { + const res = extractPackageFile(matrixYAMLwithNodeSyntaxString); + expect(res).toEqual({ + deps: [ + { + currentValue: '11.10.1', + datasource: 'github-tags', + depName: 'node', + lookupName: 'nodejs/node', + }, + ], + }); + }); + + it('handles matrix node_js syntax with node_js array', () => { + const res = extractPackageFile(matrixYAMLwithNodeSyntaxArray); + expect(res).toEqual({ + deps: [ + { + currentValue: '11.10.1', + datasource: 'github-tags', + depName: 'node', + lookupName: 'nodejs/node', + }, + { + currentValue: '11.10.2', + datasource: 'github-tags', + depName: 'node', + lookupName: 'nodejs/node', + }, + ], + }); + }); + + it('handles matrix node_js syntax with node_js array 2', () => { + const res = extractPackageFile(matrixYAMLwithNodeSyntaxArray2); + expect(res).toEqual({ + deps: [ + { + currentValue: '11.10.1', + datasource: 'github-tags', + depName: 'node', + lookupName: 'nodejs/node', + }, + { + currentValue: '11.10.2', + datasource: 'github-tags', + depName: 'node', + lookupName: 'nodejs/node', + }, + ], + }); + }); + + it('handles matrix node_js syntax with alias', () => { + const res = extractPackageFile(matrixYAMLwithNodeSyntaxAlias); + expect(res).toEqual({ + deps: [ + { + currentValue: '11.10.1', + datasource: 'github-tags', + depName: 'node', + lookupName: 'nodejs/node', + }, + ], + }); + }); + + it('handles invalid matrix node_js syntax', () => { + const res = extractPackageFile(invalidMatrixYAML); + expect(res).toBeNull(); + }); }); }); diff --git a/lib/manager/travis/extract.ts b/lib/manager/travis/extract.ts index 58ffe64021f236..c50ad28bf02202 100644 --- a/lib/manager/travis/extract.ts +++ b/lib/manager/travis/extract.ts @@ -3,12 +3,14 @@ import { load } from 'js-yaml'; import * as datasourceGithubTags from '../../datasource/github-tags'; import { logger } from '../../logger'; import type { PackageDependency, PackageFile } from '../types'; +import type { TravisMatrixItem, TravisYaml } from './types'; export function extractPackageFile(content: string): PackageFile | null { - // TODO: fix type - let doc: any; + let doc: TravisYaml | null; try { - doc = load(content, { json: true }); + doc = load(content, { + json: true, + }); } catch (err) { logger.warn({ err, content }, 'Failed to parse .travis.yml file.'); return null; @@ -22,6 +24,41 @@ export function extractPackageFile(content: string): PackageFile | null { currentValue: currentValue.toString(), })); } + + // Handle the matrix syntax + let matrix_include: TravisMatrixItem[] | undefined; + if (doc?.jobs?.include) { + matrix_include = doc.jobs.include; + } else if (doc?.matrix?.include) { + matrix_include = doc.matrix.include; + } + + if (!is.array(matrix_include)) { + return deps.length ? { deps } : null; + } + + for (const item of matrix_include) { + if (item?.node_js) { + if (is.array(item.node_js)) { + item.node_js.forEach((currentValue) => { + deps.push({ + depName: 'node', + datasource: datasourceGithubTags.id, + lookupName: 'nodejs/node', + currentValue: currentValue.toString(), + }); + }); + } else if (is.string(item.node_js)) { + deps.push({ + depName: 'node', + datasource: datasourceGithubTags.id, + lookupName: 'nodejs/node', + currentValue: item.node_js.toString(), + }); + } + } + } + if (!deps.length) { return null; } diff --git a/lib/manager/travis/readme.md b/lib/manager/travis/readme.md index fb728f0b743555..6e5cbaed906c63 100644 --- a/lib/manager/travis/readme.md +++ b/lib/manager/travis/readme.md @@ -1,14 +1,14 @@ This manager is intended to keep Travis config files (`.travis.yml`) up-to-date, this file controls the CI build environment. Currently Renovate can only update the `node_js` section of this file. -An important limitation is that Renovate does not currently "understand" [Travis's Build Matrix concept](https://docs.travis-ci.com/user/build-matrix/#matrix-expansion), so it will try to update all found Node.js versions to the latest LTS, e.g. +Renovate "understands" [Travis's Build Matrix concept](https://docs.travis-ci.com/user/build-matrix/#matrix-expansion) as well, so it will try to update all found Node.js versions to the latest LTS, e.g. ```diff node_js: - - 8.10.0 - - 10.10.0 -+ - 14.17.4 -+ - 14.17.4 ++ - 16.13.0 ++ - 16.13.0 ``` Due to this, major updates for Travis are disabled by default. @@ -24,5 +24,3 @@ Here's how to enable major updates in your Renovate config: } } ``` - -If you would like to see "build matrix" support in future, please contribute ideas to [issue #11175](https://github.com/renovatebot/renovate/issues/11175). diff --git a/lib/manager/travis/types.ts b/lib/manager/travis/types.ts new file mode 100644 index 00000000000000..937eb4ded0b39f --- /dev/null +++ b/lib/manager/travis/types.ts @@ -0,0 +1,19 @@ +// travis.yml syntax description: +// - regular: https://docs.travis-ci.com/user/tutorial/ +// - matrix: https://docs.travis-ci.com/user/build-matrix/ + +export type TravisNodeJs = string | string[]; + +export interface TravisYaml { + node_js?: TravisNodeJs; + jobs?: TravisMatrix; + matrix?: TravisMatrix; +} + +export interface TravisMatrixItem { + node_js?: TravisNodeJs; +} + +export interface TravisMatrix { + include?: TravisMatrixItem[]; +} diff --git a/lib/manager/types.ts b/lib/manager/types.ts index baddd9bc1fe8b4..77f848f3569445 100644 --- a/lib/manager/types.ts +++ b/lib/manager/types.ts @@ -6,7 +6,7 @@ import type { } from '../config/types'; import type { ProgrammingLanguage } from '../constants'; import type { RangeStrategy, SkipReason } from '../types'; -import type { File } from '../util/git'; +import type { File } from '../util/git/types'; export type Result = T | Promise; @@ -44,6 +44,7 @@ export interface UpdateArtifactsConfig { composerIgnorePlatformReqs?: string[]; currentValue?: string; postUpdateOptions?: string[]; + ignorePlugins?: boolean; ignoreScripts?: boolean; updateType?: UpdateType; newValue?: string; @@ -130,9 +131,11 @@ export interface LookupUpdate { isPin?: boolean; isRange?: boolean; isRollback?: boolean; + isReplacement?: boolean; newDigest?: string; newMajor?: number; newMinor?: number; + newName?: string; newValue: string; semanticCommitType?: string; pendingChecks?: boolean; @@ -177,6 +180,7 @@ export interface Upgrade> newDigest?: string; newFrom?: string; newMajor?: number; + newName?: string; newValue?: string; packageFile?: string; rangeStrategy?: RangeStrategy; diff --git a/lib/platform/azure/__snapshots__/index.spec.ts.snap b/lib/platform/azure/__snapshots__/index.spec.ts.snap index 8188db55f14855..e40a8dc126c859 100644 --- a/lib/platform/azure/__snapshots__/index.spec.ts.snap +++ b/lib/platform/azure/__snapshots__/index.spec.ts.snap @@ -65,14 +65,6 @@ Object { "displayNumber": "Pull Request #456", "number": 456, "pullRequestId": 456, - "reviewers": Array [ - Object { - "isFlagged": false, - "isRequired": false, - "reviewerUrl": "user-url", - "vote": 10, - }, - ], "sourceBranch": undefined, "sourceRefName": undefined, "state": "open", @@ -214,6 +206,13 @@ Array [ Array [ "123456", "file.json", + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, ], ] `; diff --git a/lib/platform/azure/azure-helper.spec.ts b/lib/platform/azure/azure-helper.spec.ts index 1476ccfa8fc762..e4e2776a77931f 100644 --- a/lib/platform/azure/azure-helper.spec.ts +++ b/lib/platform/azure/azure-helper.spec.ts @@ -78,8 +78,7 @@ describe('platform/azure/azure-helper', () => { let eventCount = 0; const mockEventStream = new Readable({ objectMode: true, - /* eslint-disable func-names */ - /* eslint-disable object-shorthand */ + read: function () { if (eventCount < 1) { eventCount += 1; @@ -108,8 +107,7 @@ describe('platform/azure/azure-helper', () => { let eventCount = 0; const mockEventStream = new Readable({ objectMode: true, - /* eslint-disable func-names */ - /* eslint-disable object-shorthand */ + read: function () { if (eventCount < 1) { eventCount += 1; @@ -138,8 +136,7 @@ describe('platform/azure/azure-helper', () => { let eventCount = 0; const mockEventStream = new Readable({ objectMode: true, - /* eslint-disable func-names */ - /* eslint-disable object-shorthand */ + read: function () { if (eventCount < 1) { eventCount += 1; @@ -235,8 +232,47 @@ describe('platform/azure/azure-helper', () => { GitPullRequestMergeStrategy.Squash ); }); + it('should return default branch policy', async () => { + azureApi.policyApi.mockImplementationOnce( + () => + ({ + getPolicyConfigurations: jest.fn(() => [ + { + settings: { + allowSquash: true, + scope: [ + { + repositoryId: 'doo-dee-doo-repository-id', + }, + ], + }, + type: { + id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab', + }, + }, + { + settings: { + allowRebase: true, + scope: [ + { + matchKind: 'DefaultBranch', + }, + ], + }, + type: { + id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab', + }, + }, + ]), + } as any) + ); + expect(await azureHelper.getMergeMethod('', '')).toEqual( + GitPullRequestMergeStrategy.Rebase + ); + }); it('should return most specific exact branch policy', async () => { const refMock = 'refs/heads/ding'; + const defaultBranchMock = 'dong'; azureApi.policyApi.mockImplementationOnce( () => ({ @@ -267,6 +303,19 @@ describe('platform/azure/azure-helper', () => { id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab', }, }, + { + settings: { + allowSquash: true, + scope: [ + { + matchKind: 'DefaultBranch', + }, + ], + }, + type: { + id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab', + }, + }, { settings: { allowRebase: true, @@ -285,12 +334,13 @@ describe('platform/azure/azure-helper', () => { ]), } as any) ); - expect(await azureHelper.getMergeMethod('', '', refMock)).toEqual( - GitPullRequestMergeStrategy.Rebase - ); + expect( + await azureHelper.getMergeMethod('', '', refMock, defaultBranchMock) + ).toEqual(GitPullRequestMergeStrategy.Rebase); }); it('should return most specific prefix branch policy', async () => { const refMock = 'refs/heads/ding-wow'; + const defaultBranchMock = 'dong-wow'; azureApi.policyApi.mockImplementationOnce( () => ({ @@ -308,6 +358,19 @@ describe('platform/azure/azure-helper', () => { id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab', }, }, + { + settings: { + allowSquash: true, + scope: [ + { + matchKind: 'DefaultBranch', + }, + ], + }, + type: { + id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab', + }, + }, { settings: { allowRebase: true, @@ -326,9 +389,9 @@ describe('platform/azure/azure-helper', () => { ]), } as any) ); - expect(await azureHelper.getMergeMethod('', '', refMock)).toEqual( - GitPullRequestMergeStrategy.Rebase - ); + expect( + await azureHelper.getMergeMethod('', '', refMock, defaultBranchMock) + ).toEqual(GitPullRequestMergeStrategy.Rebase); }); }); }); diff --git a/lib/platform/azure/azure-helper.ts b/lib/platform/azure/azure-helper.ts index ce6b6089f61fa7..43540132a83972 100644 --- a/lib/platform/azure/azure-helper.ts +++ b/lib/platform/azure/azure-helper.ts @@ -110,14 +110,21 @@ export async function getCommitDetails( export async function getMergeMethod( repoId: string, project: string, - branchRef?: string + branchRef?: string, + defaultBranch?: string ): Promise { type Scope = { repositoryId: string; refName?: string; - matchKind: 'Prefix' | 'Exact'; + matchKind: 'Prefix' | 'Exact' | 'DefaultBranch'; }; const isRelevantScope = (scope: Scope): boolean => { + if ( + scope.matchKind === 'DefaultBranch' && + (!branchRef || branchRef === `refs/heads/${defaultBranch}`) + ) { + return true; + } if (scope.repositoryId !== repoId) { return false; } diff --git a/lib/platform/azure/index.spec.ts b/lib/platform/azure/index.spec.ts index a2a2cf2f42aeab..29b8a88a8abf29 100644 --- a/lib/platform/azure/index.spec.ts +++ b/lib/platform/azure/index.spec.ts @@ -47,7 +47,7 @@ describe('platform/azure/index', () => { }); // do we need the args? - // eslint-disable-next-line @typescript-eslint/no-unused-vars + function getRepos(_token: string, _endpoint: string) { azureApi.gitApi.mockImplementationOnce( () => @@ -668,15 +668,10 @@ describe('platform/azure/index', () => { }, }; const prUpdateResult = { - ...prResult, - reviewers: [ - { - reviewerUrl: prResult.createdBy.url, - vote: AzurePrVote.Approved, - isFlagged: false, - isRequired: false, - }, - ], + reviewerUrl: prResult.createdBy.url, + vote: AzurePrVote.Approved, + isFlagged: false, + isRequired: false, }; const updateFn = jest .fn(() => prUpdateResult) @@ -1250,6 +1245,7 @@ describe('platform/azure/index', () => { const res = await azure.getJsonFile('file.json'); expect(res).toEqual(data); }); + it('returns file content in json5 format', async () => { const json5Data = ` { @@ -1268,6 +1264,21 @@ describe('platform/azure/index', () => { const res = await azure.getJsonFile('file.json5'); expect(res).toEqual({ foo: 'bar' }); }); + + it('returns file content from branch or tag', async () => { + const data = { foo: 'bar' }; + azureApi.gitApi.mockImplementationOnce( + () => + ({ + getItemContent: jest.fn(() => + Promise.resolve(Readable.from(JSON.stringify(data))) + ), + } as any) + ); + const res = await azure.getJsonFile('file.json', undefined, 'dev'); + expect(res).toEqual(data); + }); + it('throws on malformed JSON', async () => { azureApi.gitApi.mockImplementationOnce( () => diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts index 079286f474d368..231cc0558d12ab 100644 --- a/lib/platform/azure/index.ts +++ b/lib/platform/azure/index.ts @@ -5,6 +5,7 @@ import { GitPullRequestMergeStrategy, GitStatus, GitStatusState, + GitVersionDescriptor, PullRequestStatus, } from 'azure-devops-node-api/interfaces/GitInterfaces'; import delay from 'delay'; @@ -113,7 +114,8 @@ export async function getRepos(): Promise { export async function getRawFile( fileName: string, - repoName?: string + repoName?: string, + branchOrTag?: string ): Promise { const azureApiGit = await azureApi.gitApi(); @@ -126,16 +128,32 @@ export async function getRawFile( repoId = config.repoId; } - const buf = await azureApiGit.getItemContent(repoId, fileName); + const versionDescriptor: GitVersionDescriptor = { + version: branchOrTag, + } as GitVersionDescriptor; + + const buf = await azureApiGit.getItemContent( + repoId, + fileName, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + branchOrTag ? versionDescriptor : undefined + ); + const str = await streamToString(buf); return str; } export async function getJsonFile( fileName: string, - repoName?: string + repoName?: string, + branchOrTag?: string ): Promise { - const raw = await getRawFile(fileName, repoName); + const raw = await getRawFile(fileName, repoName, branchOrTag); if (fileName.endsWith('.json5')) { return JSON5.parse(raw); } @@ -167,7 +185,9 @@ export async function initRepo({ const names = getProjectAndRepo(repository); config.defaultMergeMethod = await azureHelper.getMergeMethod( repo.id, - names.project + names.project, + null, + defaultBranch ); config.mergeMethods = {}; config.repoForceRebase = false; @@ -410,7 +430,7 @@ export async function createPr({ ); } if (platformOptions?.azureAutoApprove) { - pr = await azureApiGit.createPullRequestReviewer( + await azureApiGit.createPullRequestReviewer( { reviewerUrl: pr.createdBy.url, vote: AzurePrVote.Approved, @@ -616,7 +636,8 @@ export async function mergePr({ (config.mergeMethods[pr.targetRefName] = await azureHelper.getMergeMethod( config.repoId, config.project, - pr.targetRefName + pr.targetRefName, + config.defaultBranch )); const objToUpdate: GitPullRequest = { @@ -719,7 +740,6 @@ async function getUserIds(users: string[]): Promise { const members = await Promise.all( teams.map( async (t) => - /* eslint-disable no-return-await,@typescript-eslint/return-await */ await azureApiCore.getTeamMembersWithExtendedProperties( repo.project.id, t.id diff --git a/lib/platform/azure/util.spec.ts b/lib/platform/azure/util.spec.ts index 9cdd579a7a5481..8dad90be812e26 100644 --- a/lib/platform/azure/util.spec.ts +++ b/lib/platform/azure/util.spec.ts @@ -117,7 +117,7 @@ describe('platform/azure/util', () => { describe('streamToString', () => { it('converts Readable stream to string', async () => { const res = await streamToString(Readable.from('foobar')); - expect(res).toEqual('foobar'); + expect(res).toBe('foobar'); }); it('handles error', async () => { const stream = Readable.from('foobar'); diff --git a/lib/platform/azure/util.ts b/lib/platform/azure/util.ts index 20820ccdd9d7bd..7f0c2498e975a6 100644 --- a/lib/platform/azure/util.ts +++ b/lib/platform/azure/util.ts @@ -123,7 +123,7 @@ export async function streamToString( stream: NodeJS.ReadableStream ): Promise { const chunks: Uint8Array[] = []; - /* eslint-disable promise/avoid-new */ + const p = await new Promise((resolve, reject) => { stream.on('data', (chunk) => chunks.push(Buffer.from(chunk))); stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); diff --git a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap index ac53786634db08..a9dc459199cf13 100644 --- a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap +++ b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap @@ -2068,6 +2068,88 @@ Array [ ] `; +exports[`platform/bitbucket-server/index endpoint with no path getJsonFile() returns file content from branch or tag 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/branches/default", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/browse/file.json?limit=20000&at=dev", + }, +] +`; + +exports[`platform/bitbucket-server/index endpoint with no path getJsonFile() returns file content from given repo 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/branches/default", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/DIFFERENT/repos/repo/browse/file.json?limit=20000", + }, +] +`; + exports[`platform/bitbucket-server/index endpoint with no path getJsonFile() returns file content in json5 format 1`] = ` Array [ Object { @@ -6484,6 +6566,88 @@ Array [ ] `; +exports[`platform/bitbucket-server/index endpoint with path getJsonFile() returns file content from branch or tag 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/branches/default", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/browse/file.json?limit=20000&at=dev", + }, +] +`; + +exports[`platform/bitbucket-server/index endpoint with path getJsonFile() returns file content from given repo 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/branches/default", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/DIFFERENT/repos/repo/browse/file.json?limit=20000", + }, +] +`; + exports[`platform/bitbucket-server/index endpoint with path getJsonFile() returns file content in json5 format 1`] = ` Array [ Object { diff --git a/lib/platform/bitbucket-server/index.spec.ts b/lib/platform/bitbucket-server/index.spec.ts index 2f77e0e4d03f01..c031b8652c78da 100644 --- a/lib/platform/bitbucket-server/index.spec.ts +++ b/lib/platform/bitbucket-server/index.spec.ts @@ -2109,6 +2109,7 @@ Followed by some information. expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { const json5Data = ` { @@ -2129,6 +2130,46 @@ Followed by some information. expect(res).toEqual({ foo: 'bar' }); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('returns file content from given repo', async () => { + const data = { foo: 'bar' }; + const scope = await initRepo(); + scope + .get( + `${urlPath}/rest/api/1.0/projects/DIFFERENT/repos/repo/browse/file.json?limit=20000` + ) + .reply(200, { + isLastPage: true, + lines: [{ text: JSON.stringify(data) }], + }); + const res = await bitbucket.getJsonFile( + 'file.json', + 'DIFFERENT/repo' + ); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('returns file content from branch or tag', async () => { + const data = { foo: 'bar' }; + const scope = await initRepo(); + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/browse/file.json?limit=20000&at=dev` + ) + .reply(200, { + isLastPage: true, + lines: [{ text: JSON.stringify(data) }], + }); + const res = await bitbucket.getJsonFile( + 'file.json', + 'SOME/repo', + 'dev' + ); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + it('throws on malformed JSON', async () => { const scope = await initRepo(); scope diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts index 04b8b1450fe06b..2a01c173fc03a2 100644 --- a/lib/platform/bitbucket-server/index.ts +++ b/lib/platform/bitbucket-server/index.ts @@ -123,10 +123,14 @@ export async function getRepos(): Promise { export async function getRawFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { + const repo = repoName ?? config.repository; const [project, slug] = repo.split('/'); - const fileUrl = `./rest/api/1.0/projects/${project}/repos/${slug}/browse/${fileName}?limit=20000`; + const fileUrl = + `./rest/api/1.0/projects/${project}/repos/${slug}/browse/${fileName}?limit=20000` + + (branchOrTag ? '&at=' + branchOrTag : ''); const res = await bitbucketServerHttp.getJson(fileUrl); const { isLastPage, lines, size } = res.body; if (isLastPage) { @@ -139,9 +143,10 @@ export async function getRawFile( export async function getJsonFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { - const raw = await getRawFile(fileName, repo); + const raw = await getRawFile(fileName, repoName, branchOrTag); if (fileName.endsWith('.json5')) { return JSON5.parse(raw); } diff --git a/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap b/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap index 44df44b929326c..3d6b461911c5e1 100644 --- a/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap +++ b/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap @@ -938,6 +938,95 @@ Array [ ] `; +exports[`platform/bitbucket/index getJsonFile() returns file content from branch or tag 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo/src/dev/file.json", + }, +] +`; + +exports[`platform/bitbucket/index getJsonFile() returns file content from branch with a slash in its name 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo/refs/branches/feat/123-test", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo/src/1234567890/file.json", + }, +] +`; + +exports[`platform/bitbucket/index getJsonFile() returns file content from given repo 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/different/repo/src/HEAD/file.json", + }, +] +`; + exports[`platform/bitbucket/index getJsonFile() returns file content in json5 format 1`] = ` Array [ Object { diff --git a/lib/platform/bitbucket/index.spec.ts b/lib/platform/bitbucket/index.spec.ts index 3abe950b01aa58..2027b0fdcc6be1 100644 --- a/lib/platform/bitbucket/index.spec.ts +++ b/lib/platform/bitbucket/index.spec.ts @@ -431,7 +431,7 @@ describe('platform/bitbucket/index', () => { .reply(200); expect( await bitbucket.ensureIssue({ title: 'title', body: 'body' }) - ).toEqual('updated'); + ).toBe('updated'); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('creates new issue', async () => { @@ -456,7 +456,7 @@ describe('platform/bitbucket/index', () => { reuseTitle: 'old-title', body: 'body', }) - ).toEqual('created'); + ).toBe('created'); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('noop for existing issue', async () => { @@ -938,6 +938,7 @@ describe('platform/bitbucket/index', () => { expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { const json5Data = ` { @@ -953,6 +954,47 @@ describe('platform/bitbucket/index', () => { expect(res).toEqual({ foo: 'bar' }); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('returns file content from given repo', async () => { + const data = { foo: 'bar' }; + const scope = await initRepoMock(); + scope + .get('/2.0/repositories/different/repo/src/HEAD/file.json') + .reply(200, JSON.stringify(data)); + const res = await bitbucket.getJsonFile('file.json', 'different/repo'); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('returns file content from branch or tag', async () => { + const data = { foo: 'bar' }; + const scope = await initRepoMock(); + scope + .get('/2.0/repositories/some/repo/src/dev/file.json') + .reply(200, JSON.stringify(data)); + const res = await bitbucket.getJsonFile('file.json', 'some/repo', 'dev'); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('returns file content from branch with a slash in its name', async () => { + const data = { foo: 'bar' }; + const scope = await initRepoMock(); + scope + .get('/2.0/repositories/some/repo/refs/branches/feat/123-test') + .reply(200, JSON.stringify({ target: { hash: '1234567890' } })); + scope + .get('/2.0/repositories/some/repo/src/1234567890/file.json') + .reply(200, JSON.stringify(data)); + const res = await bitbucket.getJsonFile( + 'file.json', + 'some/repo', + 'feat/123-test' + ); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + it('throws on malformed JSON', async () => { const scope = await initRepoMock(); scope diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts index f203d77e29c7f0..0d5ae4bf0390e2 100644 --- a/lib/platform/bitbucket/index.ts +++ b/lib/platform/bitbucket/index.ts @@ -48,6 +48,8 @@ let config: utils.Config = {} as any; const defaults = { endpoint: BITBUCKET_PROD_ENDPOINT }; +const pathSeparator = '/'; + let renovateUserUuid: string; export async function initPlatform({ @@ -110,20 +112,33 @@ export async function getRepos(): Promise { export async function getRawFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { // See: https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/src/%7Bcommit%7D/%7Bpath%7D + const repo = repoName ?? config.repository; const path = fileName; - const url = `/2.0/repositories/${repo}/src/HEAD/${path}`; + + let finalBranchOrTag = branchOrTag; + if (branchOrTag?.includes(pathSeparator)) { + // Branch name contans slash, so we have to replace branch name with SHA1 of the head commit; otherwise the API will not work. + finalBranchOrTag = await getBranchCommit(branchOrTag); + } + + const url = + `/2.0/repositories/${repo}/src/` + + (finalBranchOrTag || `HEAD`) + + `/${path}`; const res = await bitbucketHttp.get(url); return res.body; } export async function getJsonFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { - const raw = await getRawFile(fileName, repo); + const raw = await getRawFile(fileName, repoName, branchOrTag); if (fileName.endsWith('.json5')) { return JSON5.parse(raw); } @@ -582,7 +597,6 @@ export async function ensureIssueClosing(title: string): Promise { } } -// eslint-disable-next-line @typescript-eslint/no-unused-vars export function addAssignees( _prNr: number, _assignees: string[] @@ -804,6 +818,8 @@ export async function mergePr({ ); logger.debug('Automerging succeeded'); } catch (err) /* istanbul ignore next */ { + logger.debug({ err }, `PR merge error`); + logger.info({ pr: prNo }, 'PR automerge failed'); return false; } return true; diff --git a/lib/platform/gitea/gitea-helper.spec.ts b/lib/platform/gitea/gitea-helper.spec.ts index 9011dd0a6d561a..0b27f26fc09927 100644 --- a/lib/platform/gitea/gitea-helper.spec.ts +++ b/lib/platform/gitea/gitea-helper.spec.ts @@ -692,7 +692,7 @@ describe('platform/gitea/gitea-helper', () => { mockRepo.full_name, mockBranch.name ); - expect(res.worstStatus).not.toEqual('unknown'); + expect(res.worstStatus).not.toBe('unknown'); expect(res.statuses).toEqual([mockCommitStatus, otherMockCommitStatus]); expect(httpMock.getTrace()).toMatchSnapshot(); }); diff --git a/lib/platform/gitea/index.md b/lib/platform/gitea/index.md index 3a45af107fe621..f79c49a29e3d77 100644 --- a/lib/platform/gitea/index.md +++ b/lib/platform/gitea/index.md @@ -1,5 +1,9 @@ # Gitea +Renovate uses modern Git upload filters to suppress large blob downloads. +For Gitea you need to manually enable upload filters. +Read the official [Gitea docs](https://docs.gitea.io/en-us/clone-filters/) for more information. + ## Unsupported platform features/concepts - **Adding reviewers to PRs not supported**: Gitea versions older than v1.14.0 do not have the required API. diff --git a/lib/platform/gitea/index.spec.ts b/lib/platform/gitea/index.spec.ts index 5b41b8fb95c59f..ec60644daf02e6 100644 --- a/lib/platform/gitea/index.spec.ts +++ b/lib/platform/gitea/index.spec.ts @@ -892,7 +892,7 @@ describe('platform/gitea/index', () => { branchName: 'some-branch', id: 1, }) - ).toEqual(true); + ).toBe(true); expect(helper.mergePR).toHaveBeenCalledTimes(1); expect(helper.mergePR).toHaveBeenCalledWith( mockRepo.full_name, @@ -910,7 +910,7 @@ describe('platform/gitea/index', () => { branchName: 'some-branch', id: 1, }) - ).toEqual(false); + ).toBe(false); }); }); @@ -973,7 +973,7 @@ describe('platform/gitea/index', () => { await initFakeRepo(); const res = await gitea.ensureIssue(mockIssue); - expect(res).toEqual('created'); + expect(res).toBe('created'); expect(helper.createIssue).toHaveBeenCalledTimes(1); expect(helper.createIssue).toHaveBeenCalledWith(mockRepo.full_name, { body: mockIssue.body, @@ -1005,7 +1005,7 @@ describe('platform/gitea/index', () => { await initFakeRepo(); const res = await gitea.ensureIssue(mockIssue); - expect(res).toEqual('created'); + expect(res).toBe('created'); expect(helper.createIssue).toHaveBeenCalledTimes(1); expect(helper.createIssue).toHaveBeenCalledWith(mockRepo.full_name, { body: mockIssue.body, @@ -1027,7 +1027,7 @@ describe('platform/gitea/index', () => { once: false, }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(helper.updateIssue).toHaveBeenCalledTimes(1); expect(helper.updateIssue).toHaveBeenCalledWith( mockRepo.full_name, @@ -1066,7 +1066,7 @@ describe('platform/gitea/index', () => { labels: ['Renovate', 'Maintenance'], }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(helper.updateIssue).toHaveBeenCalledTimes(1); expect(helper.updateIssueLabels).toHaveBeenCalledTimes(0); }); @@ -1097,7 +1097,7 @@ describe('platform/gitea/index', () => { labels: ['Renovate', 'Maintenance'], }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(helper.updateIssue).toHaveBeenCalledTimes(1); expect(helper.updateIssueLabels).toHaveBeenCalledTimes(1); expect(helper.updateIssueLabels).toHaveBeenCalledWith( @@ -1136,7 +1136,7 @@ describe('platform/gitea/index', () => { labels: ['Renovate', 'Maintenance'], }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(helper.updateIssue).toHaveBeenCalledTimes(1); expect(helper.updateIssueLabels).toHaveBeenCalledTimes(1); expect(helper.updateIssueLabels).toHaveBeenCalledWith( @@ -1161,7 +1161,7 @@ describe('platform/gitea/index', () => { once: false, }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(helper.updateIssue).toHaveBeenCalledTimes(1); expect(helper.updateIssue).toHaveBeenCalledWith( mockRepo.full_name, @@ -1296,7 +1296,7 @@ describe('platform/gitea/index', () => { describe('getRepoForceRebase', () => { it('should return false - unsupported by platform', async () => { - expect(await gitea.getRepoForceRebase()).toEqual(false); + expect(await gitea.getRepoForceRebase()).toBe(false); }); }); @@ -1315,7 +1315,7 @@ describe('platform/gitea/index', () => { }); const body = '### other-topic\n\nother-content'; - expect(res).toEqual(true); + expect(res).toBe(true); expect(helper.updateComment).not.toHaveBeenCalled(); expect(helper.createComment).toHaveBeenCalledTimes(1); expect(helper.createComment).toHaveBeenCalledWith( @@ -1338,7 +1338,7 @@ describe('platform/gitea/index', () => { topic: undefined, }); - expect(res).toEqual(true); + expect(res).toBe(true); expect(helper.updateComment).not.toHaveBeenCalled(); expect(helper.createComment).toHaveBeenCalledTimes(1); expect(helper.createComment).toHaveBeenCalledWith( @@ -1362,7 +1362,7 @@ describe('platform/gitea/index', () => { }); const body = '### some-topic\n\nsome-new-content'; - expect(res).toEqual(true); + expect(res).toBe(true); expect(helper.createComment).not.toHaveBeenCalled(); expect(helper.updateComment).toHaveBeenCalledTimes(1); expect(helper.updateComment).toHaveBeenCalledWith( @@ -1381,7 +1381,7 @@ describe('platform/gitea/index', () => { content: 'some-content', }); - expect(res).toEqual(true); + expect(res).toBe(true); expect(helper.createComment).not.toHaveBeenCalled(); expect(helper.updateComment).not.toHaveBeenCalled(); }); @@ -1395,7 +1395,7 @@ describe('platform/gitea/index', () => { content: 'some-content', }); - expect(res).toEqual(false); + expect(res).toBe(false); expect(logger.warn).toHaveBeenCalledTimes(1); }); }); @@ -1527,6 +1527,27 @@ describe('platform/gitea/index', () => { const res = await gitea.getJsonFile('file.json'); expect(res).toEqual(data); }); + + it('returns file content from given repo', async () => { + const data = { foo: 'bar' }; + helper.getRepoContents.mockResolvedValueOnce({ + contentString: JSON.stringify(data), + } as never); + await initFakeRepo({ full_name: 'different/repo' }); + const res = await gitea.getJsonFile('file.json', 'different/repo'); + expect(res).toEqual(data); + }); + + it('returns file content from branch or tag', async () => { + const data = { foo: 'bar' }; + helper.getRepoContents.mockResolvedValueOnce({ + contentString: JSON.stringify(data), + } as never); + await initFakeRepo({ full_name: 'some/repo' }); + const res = await gitea.getJsonFile('file.json', 'some/repo', 'dev'); + expect(res).toEqual(data); + }); + it('returns file content in json5 format', async () => { const json5Data = ` { diff --git a/lib/platform/gitea/index.ts b/lib/platform/gitea/index.ts index 22b23eab39f69c..0fed2bd1795185 100644 --- a/lib/platform/gitea/index.ts +++ b/lib/platform/gitea/index.ts @@ -211,17 +211,20 @@ const platform: Platform = { async getRawFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { - const contents = await helper.getRepoContents(repo, fileName); + const repo = repoName ?? config.repository; + const contents = await helper.getRepoContents(repo, fileName, branchOrTag); return contents.contentString; }, async getJsonFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { - const raw = await platform.getRawFile(fileName, repo); + const raw = await platform.getRawFile(fileName, repoName, branchOrTag); if (fileName.endsWith('.json5')) { return JSON5.parse(raw); } diff --git a/lib/platform/github/__snapshots__/index.spec.ts.snap b/lib/platform/github/__snapshots__/index.spec.ts.snap index 57fcf445b09bf3..339ec34173c28d 100644 --- a/lib/platform/github/__snapshots__/index.spec.ts.snap +++ b/lib/platform/github/__snapshots__/index.spec.ts.snap @@ -5006,6 +5006,128 @@ Array [ ] `; +exports[`platform/github/index getJsonFile() returns file content from branch or tag 1`] = ` +Array [ + Object { + "graphql": Object { + "query": Object { + "__vars": Object { + "$name": "String!", + "$owner": "String!", + }, + "repository": Object { + "__args": Object { + "name": "$name", + "owner": "$owner", + }, + "autoMergeAllowed": null, + "defaultBranchRef": Object { + "name": null, + "target": Object { + "oid": null, + }, + }, + "hasIssuesEnabled": null, + "isArchived": null, + "isFork": null, + "mergeCommitAllowed": null, + "nameWithOwner": null, + "rebaseMergeAllowed": null, + "squashMergeAllowed": null, + }, + }, + "variables": Object { + "name": "repo", + "owner": "some", + }, + }, + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "authorization": "token 123test", + "content-length": "395", + "content-type": "application/json", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "POST", + "url": "https://api.github.com/graphql", + }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "authorization": "token 123test", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/repo/contents/file.json?ref=dev", + }, +] +`; + +exports[`platform/github/index getJsonFile() returns file content from given repo 1`] = ` +Array [ + Object { + "graphql": Object { + "query": Object { + "__vars": Object { + "$name": "String!", + "$owner": "String!", + }, + "repository": Object { + "__args": Object { + "name": "$name", + "owner": "$owner", + }, + "autoMergeAllowed": null, + "defaultBranchRef": Object { + "name": null, + "target": Object { + "oid": null, + }, + }, + "hasIssuesEnabled": null, + "isArchived": null, + "isFork": null, + "mergeCommitAllowed": null, + "nameWithOwner": null, + "rebaseMergeAllowed": null, + "squashMergeAllowed": null, + }, + }, + "variables": Object { + "name": "repo", + "owner": "different", + }, + }, + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "authorization": "token 123test", + "content-length": "400", + "content-type": "application/json", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "POST", + "url": "https://api.github.com/graphql", + }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "authorization": "token 123test", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.github.com/repos/different/repo/contents/file.json", + }, +] +`; + exports[`platform/github/index getJsonFile() returns file content in json5 format 1`] = ` Array [ Object { diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts index 874e099f6174ba..97fd3541e27143 100644 --- a/lib/platform/github/index.spec.ts +++ b/lib/platform/github/index.spec.ts @@ -1104,7 +1104,7 @@ describe('platform/github/index', () => { title: 'new-title', body: 'new-content', }); - expect(res).toEqual('created'); + expect(res).toBe('created'); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('creates issue if not ensuring only once', async () => { @@ -1213,7 +1213,7 @@ describe('platform/github/index', () => { body: 'new-content', labels: ['Renovate', 'Maintenance'], }); - expect(res).toEqual('created'); + expect(res).toBe('created'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -1304,7 +1304,7 @@ describe('platform/github/index', () => { reuseTitle: 'title-2', body: 'newer-content', }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -1349,7 +1349,7 @@ describe('platform/github/index', () => { body: 'newer-content', labels: ['Renovate', 'Maintenance'], }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -1471,7 +1471,7 @@ describe('platform/github/index', () => { once: false, shouldReOpen: false, }); - expect(res).toEqual('created'); + expect(res).toBe('created'); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('does not create issue if reopen flag false and issue is already open', async () => { @@ -1816,7 +1816,7 @@ describe('platform/github/index', () => { }); expect(res).toBeDefined(); res = await github.findPr({ branchName: 'branch-b' }); - expect(res).not.toBeDefined(); + expect(res).toBeUndefined(); expect(httpMock.getTrace()).toMatchSnapshot(); }); }); @@ -2410,6 +2410,7 @@ describe('platform/github/index', () => { expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { const json5Data = ` { @@ -2427,6 +2428,36 @@ describe('platform/github/index', () => { expect(res).toEqual({ foo: 'bar' }); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('returns file content from given repo', async () => { + const data = { foo: 'bar' }; + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'different/repo'); + await github.initRepo({ + repository: 'different/repo', + token: 'token', + } as any); + scope.get('/repos/different/repo/contents/file.json').reply(200, { + content: Buffer.from(JSON.stringify(data)).toString('base64'), + }); + const res = await github.getJsonFile('file.json', 'different/repo'); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('returns file content from branch or tag', async () => { + const data = { foo: 'bar' }; + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + await github.initRepo({ repository: 'some/repo', token: 'token' } as any); + scope.get('/repos/some/repo/contents/file.json?ref=dev').reply(200, { + content: Buffer.from(JSON.stringify(data)).toString('base64'), + }); + const res = await github.getJsonFile('file.json', 'some/repo', 'dev'); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + it('throws on malformed JSON', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index d22e223870645b..ecc2895006f824 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -173,9 +173,14 @@ async function getBranchProtection( export async function getRawFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { - const url = `repos/${repo}/contents/${fileName}`; + const repo = repoName ?? config.repository; + let url = `repos/${repo}/contents/${fileName}`; + if (branchOrTag) { + url += `?ref=` + branchOrTag; + } const res = await githubApi.getJson<{ content: string }>(url); const buf = res.body.content; const str = Buffer.from(buf, 'base64').toString(); @@ -184,9 +189,10 @@ export async function getRawFile( export async function getJsonFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { - const raw = await getRawFile(fileName, repo); + const raw = await getRawFile(fileName, repoName, branchOrTag); if (fileName.endsWith('.json5')) { return JSON5.parse(raw); } diff --git a/lib/platform/github/massage-markdown-links.ts b/lib/platform/github/massage-markdown-links.ts index cbff68d00f5df6..84391ff7649ea6 100644 --- a/lib/platform/github/massage-markdown-links.ts +++ b/lib/platform/github/massage-markdown-links.ts @@ -12,10 +12,10 @@ interface UrlMatch { } const urlRegex = - /(?:https?:)?(?:\/\/)?(?:www\.)?(? { title: 'new-title', body: 'new-content', }); - expect(res).toEqual('created'); + expect(res).toBe('created'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -804,7 +804,7 @@ describe('platform/gitlab/index', () => { body: 'new-content', labels: ['Renovate', 'Maintenance'], }); - expect(res).toEqual('created'); + expect(res).toBe('created'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -832,7 +832,7 @@ describe('platform/gitlab/index', () => { title: 'title-2', body: 'newer-content', }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -861,7 +861,7 @@ describe('platform/gitlab/index', () => { body: 'newer-content', labels: ['Renovate', 'Maintenance'], }); - expect(res).toEqual('updated'); + expect(res).toBe('updated'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -890,6 +890,63 @@ describe('platform/gitlab/index', () => { expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('creates confidential issue', async () => { + httpMock + .scope(gitlabApiHost) + .get( + '/api/v4/projects/undefined/issues?per_page=100&scope=created_by_me&state=opened' + ) + .reply(200, [ + { + iid: 1, + title: 'title-1', + }, + { + iid: 2, + title: 'title-2', + }, + ]) + .post('/api/v4/projects/undefined/issues') + .reply(200); + const res = await gitlab.ensureIssue({ + title: 'new-title', + body: 'new-content', + confidential: true, + }); + expect(res).toBe('created'); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('updates confidential issue', async () => { + httpMock + .scope(gitlabApiHost) + .get( + '/api/v4/projects/undefined/issues?per_page=100&scope=created_by_me&state=opened' + ) + .reply(200, [ + { + iid: 1, + title: 'title-1', + }, + { + iid: 2, + title: 'title-2', + }, + ]) + .get('/api/v4/projects/undefined/issues/2') + .reply(200, { description: 'new-content' }) + .put('/api/v4/projects/undefined/issues/2') + .reply(200); + const res = await gitlab.ensureIssue({ + title: 'title-2', + body: 'newer-content', + labels: ['Renovate', 'Maintenance'], + confidential: true, + }); + expect(res).toBe('updated'); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); }); describe('ensureIssueClosing()', () => { it('closes issue', async () => { @@ -1870,6 +1927,16 @@ These updates have all been created already. Click a checkbox below to force a r expect(smartTruncate).toHaveBeenCalledTimes(1); expect(smartTruncate).toHaveBeenCalledWith(expect.any(String), 25000); }); + + it('truncates description for API version gt 13.4', async () => { + jest.mock('../utils/pr-body'); + const { smartTruncate } = require('../utils/pr-body'); + + await initFakePlatform('13.4.1'); + gitlab.massageMarkdown(prBody); + expect(smartTruncate).toHaveBeenCalledTimes(1); + expect(smartTruncate).toHaveBeenCalledWith(expect.any(String), 1000000); + }); }); describe('getVulnerabilityAlerts()', () => { @@ -1917,6 +1984,7 @@ These updates have all been created already. Click a checkbox below to force a r expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { const json5Data = ` { @@ -1936,6 +2004,41 @@ These updates have all been created already. Click a checkbox below to force a r expect(res).toEqual({ foo: 'bar' }); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('returns file content from given repo', async () => { + const data = { foo: 'bar' }; + const scope = await initRepo(); + scope + .get( + '/api/v4/projects/different%2Frepo/repository/files/dir%2Ffile.json?ref=HEAD' + ) + .reply(200, { + content: Buffer.from(JSON.stringify(data)).toString('base64'), + }); + const res = await gitlab.getJsonFile('dir/file.json', 'different%2Frepo'); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('returns file content from branch or tag', async () => { + const data = { foo: 'bar' }; + const scope = await initRepo(); + scope + .get( + '/api/v4/projects/some%2Frepo/repository/files/dir%2Ffile.json?ref=dev' + ) + .reply(200, { + content: Buffer.from(JSON.stringify(data)).toString('base64'), + }); + const res = await gitlab.getJsonFile( + 'dir/file.json', + 'some%2Frepo', + 'dev' + ); + expect(res).toEqual(data); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + it('throws on malformed JSON', async () => { const scope = await initRepo(); scope diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts index f9aabbdfe3a5df..e489d592cde129 100644 --- a/lib/platform/gitlab/index.ts +++ b/lib/platform/gitlab/index.ts @@ -157,10 +157,14 @@ function urlEscape(str: string): string { export async function getRawFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { const escapedFileName = urlEscape(fileName); - const url = `projects/${repo}/repository/files/${escapedFileName}?ref=HEAD`; + const repo = repoName ?? config.repository; + const url = + `projects/${repo}/repository/files/${escapedFileName}?ref=` + + (branchOrTag || `HEAD`); const res = await gitlabApi.getJson<{ content: string }>(url); const buf = res.body.content; const str = Buffer.from(buf, 'base64').toString(); @@ -169,9 +173,10 @@ export async function getRawFile( export async function getJsonFile( fileName: string, - repo: string = config.repository + repoName?: string, + branchOrTag?: string ): Promise { - const raw = await getRawFile(fileName, repo); + const raw = await getRawFile(fileName, repoName, branchOrTag); if (fileName.endsWith('.json5')) { return JSON5.parse(raw); } @@ -327,6 +332,7 @@ type BranchState = | 'pending' | 'created' | 'running' + | 'waiting_for_resource' | 'manual' | 'success' | 'failed' @@ -367,6 +373,7 @@ const gitlabToRenovateStatusMapping: Record = { created: BranchStatus.yellow, manual: BranchStatus.yellow, running: BranchStatus.yellow, + waiting_for_resource: BranchStatus.yellow, success: BranchStatus.green, failed: BranchStatus.red, canceled: BranchStatus.red, @@ -685,6 +692,8 @@ export function massageMarkdown(input: string): string { ); desc = smartTruncate(desc, 25000); + } else { + desc = smartTruncate(desc, 1000000); } return desc; @@ -860,6 +869,7 @@ export async function ensureIssue({ reuseTitle, body, labels, + confidential, }: EnsureIssueConfig): Promise<'updated' | 'created' | null> { logger.debug(`ensureIssue()`); const description = massageMarkdown(sanitize(body)); @@ -884,6 +894,7 @@ export async function ensureIssue({ title, description, labels: (labels || issue.labels || []).join(','), + confidential: confidential ?? false, }, } ); @@ -895,6 +906,7 @@ export async function ensureIssue({ title, description, labels: (labels || []).join(','), + confidential: confidential ?? false, }, }); logger.info('Issue created'); diff --git a/lib/platform/index.ts b/lib/platform/index.ts index 8b6a7241dc44d1..8764d76ae37d0f 100644 --- a/lib/platform/index.ts +++ b/lib/platform/index.ts @@ -13,7 +13,6 @@ export * from './types'; export const getPlatformList = (): string[] => Array.from(platforms.keys()); export const getPlatforms = (): Map => platforms; -// eslint-disable-next-line @typescript-eslint/naming-convention let _platform: Platform; const handler: ProxyHandler = { diff --git a/lib/platform/types.ts b/lib/platform/types.ts index 3d0e7aaaaac66e..0c0936e446beff 100644 --- a/lib/platform/types.ts +++ b/lib/platform/types.ts @@ -106,6 +106,7 @@ export interface EnsureIssueConfig { labels?: string[]; once?: boolean; shouldReOpen?: boolean; + confidential?: boolean; } export interface BranchStatusConfig { branchName: string; @@ -152,8 +153,16 @@ export interface Platform { getIssueList(): Promise; getIssue?(number: number, useCache?: boolean): Promise; getVulnerabilityAlerts(): Promise; - getRawFile(fileName: string, repo?: string): Promise; - getJsonFile(fileName: string, repo?: string): Promise; + getRawFile( + fileName: string, + repoName?: string, + branchOrTag?: string + ): Promise; + getJsonFile( + fileName: string, + repoName?: string, + branchOrTag?: string + ): Promise; initRepo(config: RepoParams): Promise; getPrList(): Promise; ensureIssueClosing(title: string): Promise; diff --git a/lib/platform/utils/pr-body.spec.ts b/lib/platform/utils/pr-body.spec.ts index 710a4ceda601dd..45bb155e7f5f0d 100644 --- a/lib/platform/utils/pr-body.spec.ts +++ b/lib/platform/utils/pr-body.spec.ts @@ -8,7 +8,7 @@ describe('platform/utils/pr-body', () => { it('truncates to 1000', () => { const body = smartTruncate(prBody, 1000); expect(body).toMatchSnapshot(); - expect(body.length < prBody.length).toEqual(true); + expect(body.length < prBody.length).toBe(true); }); it('truncates to 300 not smart', () => { @@ -19,7 +19,7 @@ describe('platform/utils/pr-body', () => { it('truncates to 10', () => { const body = smartTruncate('Lorem ipsum dolor sit amet', 10); - expect(body).toEqual('Lorem ipsu'); + expect(body).toBe('Lorem ipsu'); }); it('does not truncate', () => { diff --git a/lib/types/branch-status.ts b/lib/types/branch-status.ts index a92a3f88210512..23636ed97fd6d8 100644 --- a/lib/types/branch-status.ts +++ b/lib/types/branch-status.ts @@ -1,3 +1,6 @@ +// FIXME #12556 +/* eslint-disable @typescript-eslint/naming-convention */ + export enum BranchStatus { green = 'green', // 'success' yellow = 'yellow', // 'created', 'running' diff --git a/lib/types/errors/external-host-error.ts b/lib/types/errors/external-host-error.ts index 88f4fdb936f19f..5fd5922966a704 100644 --- a/lib/types/errors/external-host-error.ts +++ b/lib/types/errors/external-host-error.ts @@ -1,7 +1,7 @@ import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; export class ExternalHostError extends Error { - hostType: string; + hostType: string | undefined; err: Error; diff --git a/lib/util/__snapshots__/sanitize.spec.ts.snap b/lib/util/__snapshots__/sanitize.spec.ts.snap deleted file mode 100644 index a2b1fff385f13c..00000000000000 --- a/lib/util/__snapshots__/sanitize.spec.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`util/sanitize sanitizes secrets from strings 1`] = `"My token is **redacted**, username is \\"userabc\\" and password is \\"**redacted**\\" (hashed: **redacted**)"`; diff --git a/lib/util/cache/memory/index.spec.ts b/lib/util/cache/memory/index.spec.ts index 6d4fcdb2fdbdcf..09abf21e1f9c55 100644 --- a/lib/util/cache/memory/index.spec.ts +++ b/lib/util/cache/memory/index.spec.ts @@ -7,7 +7,7 @@ describe('util/cache/memory/index', () => { it('sets and gets repo cache', () => { memCache.init(); memCache.set('key2', 'value'); - expect(memCache.get('key2')).toEqual('value'); + expect(memCache.get('key2')).toBe('value'); }); it('resets', () => { memCache.init(); diff --git a/lib/util/cache/package/decorator.spec.ts b/lib/util/cache/package/decorator.spec.ts index 5921bc34651c61..8aa008b09cd3e1 100644 --- a/lib/util/cache/package/decorator.spec.ts +++ b/lib/util/cache/package/decorator.spec.ts @@ -29,14 +29,17 @@ describe('util/cache/package/decorator', () => { } const myClass = new MyClass(); expect(await myClass.getNumber()).toEqual(await myClass.getNumber()); - expect(await myClass.getNumber()).not.toBeUndefined(); + expect(await myClass.getNumber()).toBeDefined(); expect(spy).toHaveBeenCalledTimes(1); }); it('Do cache null', async () => { class MyClass { @cache({ namespace: 'namespace', key: (cacheKey, test) => cacheKey }) - public async getString(cacheKey: string, test: string): Promise { + public async getString( + cacheKey: string, + test: string | null + ): Promise { await spy(); return test; } @@ -44,15 +47,15 @@ describe('util/cache/package/decorator', () => { const myClass = new MyClass(); expect(await myClass.getString('null', null)).toBeNull(); expect(await myClass.getString('null', null)).toBeNull(); - expect(await myClass.getString('test', 'test')).toEqual('test'); - expect(await myClass.getString('test', 'test')).not.toBeUndefined(); + expect(await myClass.getString('test', 'test')).toBe('test'); + expect(await myClass.getString('test', 'test')).toBeDefined(); expect(spy).toHaveBeenCalledTimes(2); }); it('Do not cache undefined', async () => { class MyClass { @cache({ namespace: 'namespace', key: 'undefined' }) - public async getString(): Promise { + public async getString(): Promise { await spy(); return undefined; } @@ -66,7 +69,7 @@ describe('util/cache/package/decorator', () => { it('should cache function', async () => { class MyClass { @cache({ - namespace: (arg: GetReleasesConfig) => arg.registryUrl, + namespace: (arg: GetReleasesConfig) => arg.registryUrl ?? 'default', key: () => 'key', }) public async getNumber(_: GetReleasesConfig): Promise { @@ -82,7 +85,7 @@ describe('util/cache/package/decorator', () => { expect(await myClass.getNumber(getReleasesConfig)).toEqual( await myClass.getNumber(getReleasesConfig) ); - expect(await myClass.getNumber(getReleasesConfig)).not.toBeUndefined(); + expect(await myClass.getNumber(getReleasesConfig)).toBeDefined(); expect(spy).toHaveBeenCalledTimes(1); }); }); diff --git a/lib/util/cache/package/decorator.ts b/lib/util/cache/package/decorator.ts index cf7382e2d691d3..a0d8e0d0ef6f7d 100644 --- a/lib/util/cache/package/decorator.ts +++ b/lib/util/cache/package/decorator.ts @@ -101,20 +101,25 @@ export function cache({ return callback(); } - let finalNamespace: string; + let finalNamespace: string | undefined; if (is.string(namespace)) { finalNamespace = namespace; } else if (is.function_(namespace)) { finalNamespace = namespace.apply(instance, args); } - let finalKey: string; + let finalKey: string | undefined; if (is.string(key)) { finalKey = key; } else if (is.function_(key)) { finalKey = key.apply(instance, args); } + // istanbul ignore if + if (!finalNamespace || !finalKey) { + return callback(); + } + const cachedResult = await packageCache.get( finalNamespace, finalKey diff --git a/lib/util/cache/package/file.ts b/lib/util/cache/package/file.ts index 2c66f4afa31d56..99965ba0dc3109 100644 --- a/lib/util/cache/package/file.ts +++ b/lib/util/cache/package/file.ts @@ -17,7 +17,7 @@ async function rm(namespace: string, key: string): Promise { export async function get( namespace: string, key: string -): Promise { +): Promise { if (!cacheFileName) { return undefined; } diff --git a/lib/util/cache/package/index.ts b/lib/util/cache/package/index.ts index a4bf0f57405b9c..a524d9986e1c5d 100644 --- a/lib/util/cache/package/index.ts +++ b/lib/util/cache/package/index.ts @@ -10,7 +10,10 @@ function getGlobalKey(namespace: string, key: string): string { return `global%%${namespace}%%${key}`; } -export function get(namespace: string, key: string): Promise { +export async function get( + namespace: string, + key: string +): Promise { if (!cacheProxy) { return undefined; } @@ -18,21 +21,22 @@ export function get(namespace: string, key: string): Promise { if (memCache.get(globalKey) === undefined) { memCache.set(globalKey, cacheProxy.get(namespace, key)); } - return memCache.get(globalKey); + const result = await memCache.get(globalKey); + return result; } -export function set( +export async function set( namespace: string, key: string, value: unknown, minutes: number ): Promise { if (!cacheProxy) { - return undefined; + return; } const globalKey = getGlobalKey(namespace, key); memCache.set(globalKey, value); - return cacheProxy.set(namespace, key, value, minutes); + await cacheProxy.set(namespace, key, value, minutes); } export function init(config: AllConfig): void { @@ -42,7 +46,7 @@ export function init(config: AllConfig): void { get: redisCache.get, set: redisCache.set, }; - } else { + } else if (config.cacheDir) { fileCache.init(config.cacheDir); cacheProxy = { get: fileCache.get, diff --git a/lib/util/cache/package/redis.ts b/lib/util/cache/package/redis.ts index cdafdb18f81cca..95954154ea7ccc 100644 --- a/lib/util/cache/package/redis.ts +++ b/lib/util/cache/package/redis.ts @@ -25,14 +25,14 @@ async function rm(namespace: string, key: string): Promise { export async function get( namespace: string, key: string -): Promise { +): Promise { if (!client) { return undefined; } logger.trace(`cache.get(${namespace}, ${key})`); try { const res = await client?.get(getKey(namespace, key)); - const cachedValue = JSON.parse(res); + const cachedValue = res && JSON.parse(res); if (cachedValue) { if (DateTime.local() < DateTime.fromISO(cachedValue.expiry)) { logger.trace({ namespace, key }, 'Returning cached value'); diff --git a/lib/util/cache/package/types.ts b/lib/util/cache/package/types.ts index 89896e47ac5135..51fa8a0e7ca520 100644 --- a/lib/util/cache/package/types.ts +++ b/lib/util/cache/package/types.ts @@ -1,5 +1,5 @@ export interface PackageCache { - get(namespace: string, key: string): Promise; + get(namespace: string, key: string): Promise; set( namespace: string, diff --git a/lib/util/cache/repository/index.spec.ts b/lib/util/cache/repository/index.spec.ts index 2fe02ba3cd546c..af06921428db85 100644 --- a/lib/util/cache/repository/index.spec.ts +++ b/lib/util/cache/repository/index.spec.ts @@ -1,6 +1,6 @@ import * as _fs from 'fs-extra'; import { mocked } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import * as repositoryCache from '.'; jest.mock('fs-extra'); @@ -10,7 +10,7 @@ const fs = mocked(_fs); describe('util/cache/repository/index', () => { beforeEach(() => { jest.resetAllMocks(); - setGlobalConfig({ cacheDir: '/tmp/renovate/cache/' }); + GlobalConfig.set({ cacheDir: '/tmp/renovate/cache/' }); }); const config = { platform: 'github', diff --git a/lib/util/cache/repository/index.ts b/lib/util/cache/repository/index.ts index cd726ab7e034dd..e81ba7bbf015c5 100644 --- a/lib/util/cache/repository/index.ts +++ b/lib/util/cache/repository/index.ts @@ -1,6 +1,6 @@ import * as fs from 'fs-extra'; import { join } from 'upath'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import type { RenovateConfig, RepositoryCacheConfig, @@ -11,16 +11,16 @@ import type { Cache } from './types'; // Increment this whenever there could be incompatibilities between old and new cache structure export const CACHE_REVISION = 9; -let repositoryCache: RepositoryCacheConfig = 'disabled'; -let cacheFileName: string; -let cache: Cache = Object.create({}); +let repositoryCache: RepositoryCacheConfig | undefined = 'disabled'; +let cacheFileName: string | null = null; +let cache: Cache | null = Object.create({}); export function getCacheFileName(config: RenovateConfig): string { return join( - getGlobalConfig().cacheDir, + GlobalConfig.get('cacheDir'), '/renovate/repository/', config.platform, - config.repository + '.json' + `${config.repository}.json` ); } @@ -63,7 +63,7 @@ export async function initialize(config: RenovateConfig): Promise { } export function getCache(): Cache { - return cache || createCache(); + return cache ?? createCache(); } export async function finalize(): Promise { diff --git a/lib/util/clone.ts b/lib/util/clone.ts index d1c9530e7e597a..cb0b5ba7be134b 100644 --- a/lib/util/clone.ts +++ b/lib/util/clone.ts @@ -1,5 +1,5 @@ import safeStringify from 'fast-safe-stringify'; -export function clone(input: T = null): T { +export function clone(input: T | null = null): T { return JSON.parse(safeStringify(input)); } diff --git a/lib/util/emoji.spec.ts b/lib/util/emoji.spec.ts index 969563e339565a..d663320fe69593 100644 --- a/lib/util/emoji.spec.ts +++ b/lib/util/emoji.spec.ts @@ -8,7 +8,7 @@ describe('util/emoji', () => { describe('emojify', () => { it('encodes known shortcodes', () => { - expect(emojify('Let it :bee:')).toEqual('Let it 🐝'); + expect(emojify('Let it :bee:')).toBe('Let it 🐝'); }); it('encodes aliases', () => { @@ -18,12 +18,12 @@ describe('util/emoji', () => { }); it('omits unknown shortcodes', () => { - expect(emojify(':foo: :bar: :bee:')).toEqual(':foo: :bar: 🐝'); + expect(emojify(':foo: :bar: :bee:')).toBe(':foo: :bar: 🐝'); }); it('does not encode when config option is disabled', () => { setEmojiConfig({ unicodeEmoji: false }); - expect(emojify('Let it :bee:')).toEqual('Let it :bee:'); + expect(emojify('Let it :bee:')).toBe('Let it :bee:'); }); }); @@ -51,7 +51,7 @@ describe('util/emoji', () => { it('uses replacement character', () => { setEmojiConfig({ unicodeEmoji: false }); - expect(unemojify(unsupported)).toEqual('īŋŊ'); + expect(unemojify(unsupported)).toBe('īŋŊ'); }); }); }); diff --git a/lib/util/emoji.ts b/lib/util/emoji.ts index 7f6edaa8de1689..ae31076d336133 100644 --- a/lib/util/emoji.ts +++ b/lib/util/emoji.ts @@ -55,7 +55,7 @@ export function emojify(text: string): string { const emojiRegexSrc = [emojibaseEmojiRegex, mathiasBynensEmojiRegex()].map( ({ source }) => source ); -const emojiRegex = new RegExp(`(?:${emojiRegexSrc.join('|')})`, 'g'); // TODO #12070 +const emojiRegex = new RegExp(`(?:${emojiRegexSrc.join('|')})`, 'g'); // TODO #12875 cannot figure it out const excludedModifiers = new Set([ '20E3', '200D', diff --git a/lib/util/exec/buildpack.spec.ts b/lib/util/exec/buildpack.spec.ts new file mode 100644 index 00000000000000..6cc85cde5bfcb2 --- /dev/null +++ b/lib/util/exec/buildpack.spec.ts @@ -0,0 +1,105 @@ +import { mocked } from '../../../test/util'; +import * as _datasource from '../../datasource'; +import { generateInstallCommands, resolveConstraint } from './buildpack'; +import type { ToolConstraint } from './types'; + +jest.mock('../../../lib/datasource'); + +const datasource = mocked(_datasource); + +describe('util/exec/buildpack', () => { + describe('resolveConstraint()', () => { + beforeEach(() => { + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.1.0' }, + { version: '1.3.0' }, + { version: '2.0.14' }, + { version: '2.1.0' }, + ], + }); + }); + it('returns from config', async () => { + expect( + await resolveConstraint({ toolName: 'composer', constraint: '1.1.0' }) + ).toBe('1.1.0'); + }); + + it('returns from latest', async () => { + expect(await resolveConstraint({ toolName: 'composer' })).toBe('2.1.0'); + }); + + it('throws for unknown tools', async () => { + datasource.getPkgReleases.mockReset(); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [], + }); + await expect(resolveConstraint({ toolName: 'whoops' })).rejects.toThrow( + 'Invalid tool to install: whoops' + ); + }); + + it('throws no releases', async () => { + datasource.getPkgReleases.mockReset(); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [], + }); + await expect(resolveConstraint({ toolName: 'composer' })).rejects.toThrow( + 'No tool releases found.' + ); + }); + + it('falls back to latest version if no compatible release', async () => { + datasource.getPkgReleases.mockReset(); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.2.3' }], + }); + expect( + await resolveConstraint({ toolName: 'composer', constraint: '^3.1.0' }) + ).toBe('1.2.3'); + }); + + it('falls back to latest version if invalid constraint', async () => { + datasource.getPkgReleases.mockReset(); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.2.3' }], + }); + expect( + await resolveConstraint({ toolName: 'composer', constraint: 'whoops' }) + ).toBe('1.2.3'); + }); + }); + describe('generateInstallCommands()', () => { + beforeEach(() => { + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.1.0' }, + { version: '1.3.0' }, + { version: '2.0.14' }, + { version: '2.1.0' }, + ], + }); + }); + it('returns install commands', async () => { + const toolConstraints: ToolConstraint[] = [ + { + toolName: 'composer', + }, + ]; + expect(await generateInstallCommands(toolConstraints)) + .toMatchInlineSnapshot(` + Array [ + "install-tool composer 2.1.0", + ] + `); + }); + it('hashes npm', async () => { + const toolConstraints: ToolConstraint[] = [{ toolName: 'npm' }]; + const res = await generateInstallCommands(toolConstraints); + expect(res).toHaveLength(2); + expect(res[1]).toBe('hash -d npm 2>/dev/null || true'); + }); + }); +}); diff --git a/lib/util/exec/buildpack.ts b/lib/util/exec/buildpack.ts new file mode 100644 index 00000000000000..75bae696d82c64 --- /dev/null +++ b/lib/util/exec/buildpack.ts @@ -0,0 +1,90 @@ +import { quote } from 'shlex'; +import { getPkgReleases } from '../../datasource'; +import { logger } from '../../logger'; +import * as allVersioning from '../../versioning'; +import { id as composerVersioningId } from '../../versioning/composer'; +import { id as npmVersioningId } from '../../versioning/npm'; +import { id as semverVersioningId } from '../../versioning/semver'; +import type { ToolConfig, ToolConstraint } from './types'; + +const allToolConfig: Record = { + composer: { + datasource: 'github-releases', + depName: 'composer/composer', + versioning: composerVersioningId, + }, + jb: { + datasource: 'github-releases', + depName: 'jsonnet-bundler/jsonnet-bundler', + versioning: semverVersioningId, + }, + npm: { + datasource: 'npm', + depName: 'npm', + hash: true, + versioning: npmVersioningId, + }, +}; + +export async function resolveConstraint( + toolConstraint: ToolConstraint +): Promise { + const { toolName } = toolConstraint; + const toolConfig = allToolConfig[toolName]; + if (!toolConfig) { + throw new Error(`Invalid tool to install: ${toolName}`); + } + + const versioning = allVersioning.get(toolConfig.versioning); + let constraint = toolConstraint.constraint; + if (constraint) { + if (versioning.isValid(constraint)) { + if (versioning.isSingleVersion(constraint)) { + return constraint; + } + } else { + logger.warn({ toolName, constraint }, 'Invalid tool constraint'); + constraint = undefined; + } + } + + const pkgReleases = await getPkgReleases(toolConfig); + if (!pkgReleases?.releases?.length) { + throw new Error('No tool releases found.'); + } + + const allVersions = pkgReleases.releases.map((r) => r.version); + const matchingVersions = allVersions.filter( + (v) => !constraint || versioning.matches(v, constraint) + ); + + if (matchingVersions.length) { + const resolvedVersion = matchingVersions.pop(); + logger.debug({ toolName, constraint, resolvedVersion }, 'Resolved version'); + return resolvedVersion; + } + const latestVersion = allVersions.filter((v) => versioning.isStable(v)).pop(); + logger.warn( + { toolName, constraint, latestVersion }, + 'No matching tool versions found for constraint - using latest version' + ); + return latestVersion; +} + +export async function generateInstallCommands( + toolConstraints: ToolConstraint[] +): Promise { + const installCommands = []; + if (toolConstraints?.length) { + for (const toolConstraint of toolConstraints) { + const toolVersion = await resolveConstraint(toolConstraint); + const { toolName } = toolConstraint; + const installCommand = `install-tool ${toolName} ${quote(toolVersion)}`; + installCommands.push(installCommand); + if (allToolConfig[toolName].hash) { + installCommands.push(`hash -d ${toolName} 2>/dev/null || true`); + } + } + } + return installCommands; +} diff --git a/lib/util/exec/common.ts b/lib/util/exec/common.ts index f0cb2a9c853624..80e3be2e7dfe04 100644 --- a/lib/util/exec/common.ts +++ b/lib/util/exec/common.ts @@ -1,39 +1,8 @@ -import { - ExecOptions as ChildProcessExecOptions, - exec as cpExec, -} from 'child_process'; +import { exec } from 'child_process'; import { promisify } from 'util'; - -export type Opt = T | null | undefined; - -export type VolumesPair = [string, string]; -export type VolumeOption = Opt; - -export type DockerExtraCommand = Opt; -export type DockerExtraCommands = Opt; - -export interface DockerOptions { - image: string; - tag?: Opt; - tagScheme?: Opt; - tagConstraint?: Opt; - volumes?: Opt; - envVars?: Opt[]>; - cwd?: Opt; - preCommands?: DockerExtraCommands; - postCommands?: DockerExtraCommands; -} - -export interface RawExecOptions extends ChildProcessExecOptions { - encoding: string; -} - -export interface ExecResult { - stdout: string; - stderr: string; -} +import type { ExecResult, RawExecOptions } from './types'; export const rawExec: ( cmd: string, opts: RawExecOptions -) => Promise = promisify(cpExec); +) => Promise = promisify(exec); diff --git a/lib/util/exec/docker/index.spec.ts b/lib/util/exec/docker/index.spec.ts index 4c8d4dea7dfcd7..5d10725d51ac79 100644 --- a/lib/util/exec/docker/index.spec.ts +++ b/lib/util/exec/docker/index.spec.ts @@ -3,11 +3,11 @@ import { mockExecAll, mockExecSequence, } from '../../../../test/exec-util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { SYSTEM_INSUFFICIENT_MEMORY } from '../../../constants/error-messages'; import { getPkgReleases as _getPkgReleases } from '../../../datasource'; import { logger } from '../../../logger'; -import type { VolumeOption } from '../common'; +import type { VolumeOption } from '../types'; import { generateDockerCommand, getDockerTag, @@ -141,12 +141,12 @@ describe('util/exec/docker/index', () => { describe('removeDanglingContainers', () => { beforeEach(() => { - setGlobalConfig({ binarySource: 'docker' }); + GlobalConfig.set({ binarySource: 'docker' }); }); it('short-circuits in non-Docker environment', async () => { const execSnapshots = mockExecAll(exec); - setGlobalConfig({ binarySource: 'global' }); + GlobalConfig.set({ binarySource: 'global' }); await removeDanglingContainers(); expect(execSnapshots).toBeEmpty(); }); @@ -209,12 +209,9 @@ describe('util/exec/docker/index', () => { describe('generateDockerCommand', () => { const preCommands = [null, 'foo', undefined]; const commands = ['bar']; - const postCommands = [undefined, 'baz', null]; const envVars = ['FOO', 'BAR']; const image = 'sample_image'; const dockerOptions = { - preCommands, - postCommands, image, cwd: '/tmp/foobar', envVars, @@ -228,15 +225,19 @@ describe('util/exec/docker/index', () => { `-e FOO -e BAR ` + `-w "/tmp/foobar" ` + `renovate/${img} ` + - `bash -l -c "foo && bar && baz"`; + `bash -l -c "foo && bar"`; beforeEach(() => { - setGlobalConfig({ dockerUser: 'some-user' }); + GlobalConfig.set({ dockerUser: 'some-user' }); }); it('returns executable command', async () => { mockExecAll(exec); - const res = await generateDockerCommand(commands, dockerOptions); + const res = await generateDockerCommand( + commands, + preCommands, + dockerOptions + ); expect(res).toBe(command(image)); }); @@ -247,7 +248,7 @@ describe('util/exec/docker/index', () => { ['/tmp/bar', `/tmp/bar`], ['/tmp/baz', `/home/baz`], ]; - const res = await generateDockerCommand(commands, { + const res = await generateDockerCommand(commands, preCommands, { ...dockerOptions, volumes: [...volumes, ...volumes], }); @@ -261,7 +262,7 @@ describe('util/exec/docker/index', () => { it('handles tag parameter', async () => { mockExecAll(exec); - const res = await generateDockerCommand(commands, { + const res = await generateDockerCommand(commands, preCommands, { ...dockerOptions, tag: '1.2.3', }); @@ -277,7 +278,7 @@ describe('util/exec/docker/index', () => { { version: '2.0.0' }, ], } as never); - const res = await generateDockerCommand(commands, { + const res = await generateDockerCommand(commands, preCommands, { ...dockerOptions, tagScheme: 'npm', tagConstraint: '^1.2.3', diff --git a/lib/util/exec/docker/index.ts b/lib/util/exec/docker/index.ts index 50ca989205ca8e..bae3aea2f1e9b4 100644 --- a/lib/util/exec/docker/index.ts +++ b/lib/util/exec/docker/index.ts @@ -1,18 +1,13 @@ import is from '@sindresorhus/is'; -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { SYSTEM_INSUFFICIENT_MEMORY } from '../../../constants/error-messages'; import { getPkgReleases } from '../../../datasource'; import { logger } from '../../../logger'; import * as versioning from '../../../versioning'; import { regEx } from '../../regex'; import { ensureTrailingSlash } from '../../url'; -import { - DockerOptions, - Opt, - VolumeOption, - VolumesPair, - rawExec, -} from '../common'; +import { rawExec } from '../common'; +import type { DockerOptions, Opt, VolumeOption, VolumesPair } from '../types'; const prefetchedImages = new Set(); @@ -76,7 +71,10 @@ export async function getDockerTag( const ver = versioning.get(scheme); if (!ver.isValid(constraint)) { - logger.warn({ constraint }, `Invalid ${scheme} version constraint`); + logger.warn( + { scheme, constraint }, + `Invalid Docker image version constraint` + ); return 'latest'; } @@ -156,7 +154,7 @@ export async function removeDockerContainer( } export async function removeDanglingContainers(): Promise { - const { binarySource, dockerChildPrefix } = getGlobalConfig(); + const { binarySource, dockerChildPrefix } = GlobalConfig.get(); if (binarySource !== 'docker') { return; } @@ -196,20 +194,19 @@ export async function removeDanglingContainers(): Promise { export async function generateDockerCommand( commands: string[], + preCommands: string[], options: DockerOptions ): Promise { const { envVars, cwd, tagScheme, tagConstraint } = options; let image = options.image; const volumes = options.volumes || []; - const preCommands = options.preCommands || []; - const postCommands = options.postCommands || []; const { localDir, cacheDir, dockerUser, dockerChildPrefix, dockerImagePrefix, - } = getGlobalConfig(); + } = GlobalConfig.get(); const result = ['docker run --rm']; const containerName = getContainerName(image, dockerChildPrefix); const containerLabel = getContainerLabel(dockerChildPrefix); @@ -253,11 +250,9 @@ export async function generateDockerCommand( await prefetchDockerImage(taggedImage); result.push(taggedImage); - const bashCommand = [ - ...prepareCommands(preCommands), - ...commands, - ...prepareCommands(postCommands), - ].join(' && '); + const bashCommand = [...prepareCommands(preCommands), ...commands].join( + ' && ' + ); result.push(`bash -l -c "${bashCommand.replace(regEx(/"/g), '\\"')}"`); // lgtm [js/incomplete-sanitization] return result.join(' '); diff --git a/lib/util/exec/env.spec.ts b/lib/util/exec/env.spec.ts index d6a4d09bf52c5f..ac221486e9c6c1 100644 --- a/lib/util/exec/env.spec.ts +++ b/lib/util/exec/env.spec.ts @@ -1,4 +1,4 @@ -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { getChildProcessEnv } from './env'; describe('util/exec/env', () => { @@ -54,7 +54,7 @@ describe('util/exec/env', () => { describe('getChildProcessEnv when trustlevel set to high', () => { it('returns process.env if trustlevel set to high', () => { - setGlobalConfig({ exposeAllEnv: true }); + GlobalConfig.set({ exposeAllEnv: true }); expect(getChildProcessEnv()).toMatchObject(process.env); }); }); diff --git a/lib/util/exec/env.ts b/lib/util/exec/env.ts index f453bd34abc34a..7fef55acf6f7ac 100644 --- a/lib/util/exec/env.ts +++ b/lib/util/exec/env.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; const basicEnvVars = [ 'HTTP_PROXY', @@ -20,7 +20,7 @@ export function getChildProcessEnv( customEnvVars: string[] = [] ): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = {}; - if (getGlobalConfig().exposeAllEnv) { + if (GlobalConfig.get('exposeAllEnv')) { return { ...env, ...process.env }; } const envVars = [...basicEnvVars, ...customEnvVars]; diff --git a/lib/util/exec/index.spec.ts b/lib/util/exec/index.spec.ts index 6480b8b54cd9ab..556ec654477136 100644 --- a/lib/util/exec/index.spec.ts +++ b/lib/util/exec/index.spec.ts @@ -1,15 +1,14 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import { ExecOptions as ChildProcessExecOptions, exec as _cpExec, } from 'child_process'; import { envMock } from '../../../test/exec-util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; -import { RawExecOptions, VolumeOption } from './common'; import * as dockerModule from './docker'; -import { ExecOptions, exec } from '.'; +import type { ExecOptions, RawExecOptions, VolumeOption } from './types'; +import { exec } from '.'; const cpExec: jest.Mock = _cpExec as any; @@ -39,7 +38,7 @@ describe('util/exec/index', () => { jest.restoreAllMocks(); jest.resetModules(); processEnvOrig = process.env; - setGlobalConfig(); + GlobalConfig.reset(); }); afterEach(() => { @@ -465,14 +464,13 @@ describe('util/exec/index', () => { inOpts: { docker: { image, - preCommands: ['preCommand1', 'preCommand2', null], - postCommands: ['postCommand1', undefined, 'postCommand2'], }, + preCommands: ['preCommand1', 'preCommand2', null], }, outCmd: [ dockerPullCmd, dockerRemoveCmd, - `docker run --rm --name=${name} --label=renovate_child ${defaultVolumes} -w "${cwd}" ${fullImage} bash -l -c "preCommand1 && preCommand2 && ${inCmd} && postCommand1 && postCommand2"`, + `docker run --rm --name=${name} --label=renovate_child ${defaultVolumes} -w "${cwd}" ${fullImage} bash -l -c "preCommand1 && preCommand2 && ${inCmd}"`, ], outOpts: [ dockerPullOpts, @@ -497,8 +495,6 @@ describe('util/exec/index', () => { inOpts: { docker: { image, - preCommands: null, - postCommands: undefined, }, }, outCmd: [ @@ -543,6 +539,26 @@ describe('util/exec/index', () => { }, ], + [ + 'Default timeout from executionTimeout config option', + { + processEnv, + inCmd, + inOpts: {}, + outCmd, + outOpts: [ + { + cwd, + encoding, + env: envMock.basic, + timeout: 30 * 60 * 1000, + maxBuffer: 10485760, + }, + ], + adminConfig: { executionTimeout: 30 }, + }, + ], + [ 'Explicit maxBuffer', { @@ -698,7 +714,7 @@ describe('util/exec/index', () => { callback(null, { stdout: '', stderr: '' }); return undefined; }); - setGlobalConfig({ cacheDir, localDir: cwd, ...adminConfig }); + GlobalConfig.set({ cacheDir, localDir: cwd, ...adminConfig }); await exec(cmd as string, inOpts); expect(actualCmd).toEqual(outCommand); @@ -715,19 +731,19 @@ describe('util/exec/index', () => { return undefined; }); - setGlobalConfig({ binarySource: 'global' }); + GlobalConfig.set({ binarySource: 'global' }); await exec(inCmd, { docker }); await exec(inCmd, { docker }); - setGlobalConfig({ binarySource: 'docker' }); + GlobalConfig.set({ binarySource: 'docker' }); await exec(inCmd, { docker }); await exec(inCmd, { docker }); - setGlobalConfig({ binarySource: 'global' }); + GlobalConfig.set({ binarySource: 'global' }); await exec(inCmd, { docker }); await exec(inCmd, { docker }); - setGlobalConfig({ binarySource: 'docker' }); + GlobalConfig.set({ binarySource: 'docker' }); await exec(inCmd, { docker }); await exec(inCmd, { docker }); @@ -751,7 +767,7 @@ describe('util/exec/index', () => { }); it('wraps error if removeDockerContainer throws an error', async () => { - setGlobalConfig({ binarySource: 'docker' }); + GlobalConfig.set({ binarySource: 'docker' }); cpExec.mockImplementation(() => { throw new Error('some error occurred'); }); diff --git a/lib/util/exec/index.ts b/lib/util/exec/index.ts index b086f22b1b4fee..a04b9b710a0b63 100644 --- a/lib/util/exec/index.ts +++ b/lib/util/exec/index.ts @@ -1,31 +1,24 @@ -import type { ExecOptions as ChildProcessExecOptions } from 'child_process'; import { dirname, join } from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; import { logger } from '../../logger'; -import { +import { generateInstallCommands } from './buildpack'; +import { rawExec } from './common'; +import { generateDockerCommand, removeDockerContainer } from './docker'; +import { getChildProcessEnv } from './env'; +import type { DockerOptions, + ExecOptions, ExecResult, - Opt, + ExtraEnv, RawExecOptions, - rawExec, -} from './common'; -import { generateDockerCommand, removeDockerContainer } from './docker'; -import { getChildProcessEnv } from './env'; - -type ExtraEnv = Record; - -export interface ExecOptions extends ChildProcessExecOptions { - cwdFile?: string; - extraEnv?: Opt; - docker?: Opt; -} +} from './types'; function getChildEnv({ extraEnv = {}, env: forcedEnv = {}, }: ExecOptions): ExtraEnv { - const { customEnvVariables: globalConfigEnv } = getGlobalConfig(); + const globalConfigEnv = GlobalConfig.get('customEnvVariables'); const inheritedKeys = Object.entries(extraEnv).reduce( (acc, [key, val]) => @@ -59,34 +52,38 @@ function dockerEnvVars( } function getCwd({ cwd, cwdFile }: ExecOptions): string { - const { localDir: defaultCwd } = getGlobalConfig(); + const defaultCwd = GlobalConfig.get('localDir'); const paramCwd = cwdFile ? join(defaultCwd, dirname(cwdFile)) : cwd; return paramCwd || defaultCwd; } function getRawExecOptions(opts: ExecOptions): RawExecOptions { - const execOptions: ExecOptions = { ...opts }; - delete execOptions.extraEnv; - delete execOptions.docker; - delete execOptions.cwdFile; - + const defaultExecutionTimeout = GlobalConfig.get('executionTimeout'); const childEnv = getChildEnv(opts); const cwd = getCwd(opts); const rawExecOptions: RawExecOptions = { + cwd, encoding: 'utf-8', - ...execOptions, env: childEnv, - cwd, + maxBuffer: opts.maxBuffer, + timeout: opts.timeout, }; - // Set default timeout to 15 minutes - rawExecOptions.timeout = rawExecOptions.timeout || 15 * 60 * 1000; + // Set default timeout config.executionTimeout if specified; othrwise to 15 minutes + if (!rawExecOptions.timeout) { + if (defaultExecutionTimeout) { + rawExecOptions.timeout = defaultExecutionTimeout * 60 * 1000; + } else { + rawExecOptions.timeout = 15 * 60 * 1000; + } + } + // Set default max buffer size to 10MB rawExecOptions.maxBuffer = rawExecOptions.maxBuffer || 10 * 1024 * 1024; return rawExecOptions; } function isDocker({ docker }: ExecOptions): boolean { - const { binarySource } = getGlobalConfig(); + const { binarySource } = GlobalConfig.get(); return binarySource === 'docker' && !!docker; } @@ -100,7 +97,7 @@ async function prepareRawExec( opts: ExecOptions = {} ): Promise { const { docker } = opts; - const { customEnvVariables } = getGlobalConfig(); + const { customEnvVariables } = GlobalConfig.get(); const rawOptions = getRawExecOptions(opts); @@ -113,9 +110,13 @@ async function prepareRawExec( const envVars = dockerEnvVars(extraEnv, childEnv); const cwd = getCwd(opts); const dockerOptions: DockerOptions = { ...docker, cwd, envVars }; - + const preCommands = [ + ...(await generateInstallCommands(opts.toolConstraints)), + ...(opts.preCommands || []), + ]; const dockerCommand = await generateDockerCommand( rawCommands, + preCommands, dockerOptions ); rawCommands = [dockerCommand]; @@ -129,7 +130,7 @@ export async function exec( opts: ExecOptions = {} ): Promise { const { docker } = opts; - const { dockerChildPrefix } = getGlobalConfig(); + const { dockerChildPrefix } = GlobalConfig.get(); const { rawCommands, rawOptions } = await prepareRawExec(cmd, opts); const useDocker = isDocker(opts); diff --git a/lib/util/exec/types.ts b/lib/util/exec/types.ts new file mode 100644 index 00000000000000..39847e8be650d3 --- /dev/null +++ b/lib/util/exec/types.ts @@ -0,0 +1,55 @@ +import type { ExecOptions as ChildProcessExecOptions } from 'child_process'; + +export interface ToolConstraint { + toolName: string; + constraint?: string; +} + +export interface ToolConfig { + datasource: string; + depName: string; + hash?: boolean; + versioning: string; +} + +export type Opt = T | null | undefined; + +export type VolumesPair = [string, string]; +export type VolumeOption = Opt; + +export type DockerExtraCommand = Opt; +export type DockerExtraCommands = Opt; + +export interface DockerOptions { + image: string; + tag?: Opt; + tagScheme?: Opt; + tagConstraint?: Opt; + volumes?: Opt; + envVars?: Opt[]>; + cwd?: Opt; +} + +export interface RawExecOptions extends ChildProcessExecOptions { + encoding: string; +} + +export interface ExecResult { + stdout: string; + stderr: string; +} + +export type ExtraEnv = Record; + +export interface ExecOptions { + cwd?: string; + cwdFile?: string; + env?: Opt; + extraEnv?: Opt; + docker?: Opt; + toolConstraints?: Opt; + preCommands?: DockerExtraCommands; + // Following are pass-through to child process + maxBuffer?: number | undefined; + timeout?: number | undefined; +} diff --git a/lib/util/fs/index.spec.ts b/lib/util/fs/index.spec.ts index 1a7732410c5dfd..6275b0edb87486 100644 --- a/lib/util/fs/index.spec.ts +++ b/lib/util/fs/index.spec.ts @@ -2,7 +2,7 @@ import { withDir } from 'tmp-promise'; import { join } from 'upath'; import { envMock } from '../../../test/exec-util'; import { mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import * as _env from '../exec/env'; import { ensureCacheDir, @@ -22,7 +22,7 @@ const env = mocked(_env); describe('util/fs/index', () => { describe('readLocalFile', () => { beforeEach(() => { - setGlobalConfig({ localDir: '' }); + GlobalConfig.set({ localDir: '' }); }); it('reads buffer', async () => { @@ -56,7 +56,7 @@ describe('util/fs/index', () => { it('returns path for file', async () => { await withDir( async (localDir) => { - setGlobalConfig({ + GlobalConfig.set({ localDir: localDir.path, }); @@ -110,7 +110,7 @@ describe('util/fs/index', () => { it('returns dir content', async () => { await withDir( async (localDir) => { - setGlobalConfig({ + GlobalConfig.set({ localDir: localDir.path, }); await writeLocalFile('test/Cargo.toml', ''); @@ -138,7 +138,7 @@ describe('util/fs/index', () => { it('return empty array for non existing directory', async () => { await withDir( async (localDir) => { - setGlobalConfig({ + GlobalConfig.set({ localDir: localDir.path, }); await expect(readLocalDirectory('somedir')).rejects.toThrow(); @@ -170,7 +170,7 @@ describe('util/fs/index', () => { ...envMock.basic, }); - setGlobalConfig({ + GlobalConfig.set({ cacheDir: join(dirFromConfig), }); diff --git a/lib/util/fs/index.ts b/lib/util/fs/index.ts index 03e2d6be351bd0..114ed425b0c882 100644 --- a/lib/util/fs/index.ts +++ b/lib/util/fs/index.ts @@ -3,7 +3,7 @@ import util from 'util'; import is from '@sindresorhus/is'; import * as fs from 'fs-extra'; import { isAbsolute, join, parse } from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; export * from './proxies'; @@ -30,11 +30,13 @@ export async function readLocalFile( export async function readLocalFile( fileName: string, encoding?: string -): Promise { - const { localDir } = getGlobalConfig(); +): Promise { + const { localDir } = GlobalConfig.get(); const localFileName = join(localDir, fileName); try { - const fileContent = await fs.readFile(localFileName, encoding); + const fileContent = encoding + ? await fs.readFile(localFileName, encoding) + : await fs.readFile(localFileName); return fileContent; } catch (err) { logger.trace({ err }, 'Error reading local file'); @@ -46,13 +48,13 @@ export async function writeLocalFile( fileName: string, fileContent: string ): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const localFileName = join(localDir, fileName); await fs.outputFile(localFileName, fileContent); } export async function deleteLocalFile(fileName: string): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); if (localDir) { const localFileName = join(localDir, fileName); await fs.remove(localFileName); @@ -64,7 +66,7 @@ export async function renameLocalFile( fromFile: string, toFile: string ): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); await fs.move(join(localDir, fromFile), join(localDir, toFile)); } @@ -77,13 +79,13 @@ export async function ensureDir(dirName: string): Promise { // istanbul ignore next export async function ensureLocalDir(dirName: string): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const localDirName = join(localDir, dirName); await fs.ensureDir(localDirName); } export async function ensureCacheDir(name: string): Promise { - const cacheDirName = join(getGlobalConfig().cacheDir, `others/${name}`); + const cacheDirName = join(GlobalConfig.get('cacheDir'), `others/${name}`); await fs.ensureDir(cacheDirName); return cacheDirName; } @@ -94,12 +96,12 @@ export async function ensureCacheDir(name: string): Promise { * without risk of that information leaking to other repositories/users. */ export function privateCacheDir(): string { - const { cacheDir } = getGlobalConfig(); + const { cacheDir } = GlobalConfig.get(); return join(cacheDir, '__renovate-private-cache'); } export function localPathExists(pathName: string): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); // Works for both files as well as directories return fs .stat(join(localDir, pathName)) @@ -140,7 +142,7 @@ export async function findLocalSiblingOrParent( * Get files by name from directory */ export async function readLocalDirectory(path: string): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const localPath = join(localDir, path); const fileList = await fs.readdir(localPath); return fileList; diff --git a/lib/util/fs/proxies.ts b/lib/util/fs/proxies.ts index 3abe0f4624a97c..4008ec28950772 100644 --- a/lib/util/fs/proxies.ts +++ b/lib/util/fs/proxies.ts @@ -23,7 +23,7 @@ export function readFile( fileName: string, encoding?: string ): Promise { - return fs.readFile(fileName, encoding); + return encoding ? fs.readFile(fileName, encoding) : fs.readFile(fileName); } // istanbul ignore next diff --git a/lib/util/git/author.ts b/lib/util/git/author.ts index f029ebc3363762..26860683b635e7 100644 --- a/lib/util/git/author.ts +++ b/lib/util/git/author.ts @@ -1,11 +1,7 @@ import addrs from 'email-addresses'; import { logger } from '../../logger'; import { regEx } from '../regex'; - -export interface GitAuthor { - name?: string; - address?: string; -} +import type { GitAuthor } from './types'; export function parseGitAuthor(input: string): GitAuthor | null { let result: GitAuthor = null; diff --git a/lib/util/git/config.ts b/lib/util/git/config.ts index 7842559b577edb..056189cfca23d9 100644 --- a/lib/util/git/config.ts +++ b/lib/util/git/config.ts @@ -1,15 +1,8 @@ import is from '@sindresorhus/is'; import { SimpleGitOptions } from 'simple-git'; +import type { GitNoVerifyOption } from './types'; -export const enum GitNoVerifyOption { - Commit = 'commit', - Push = 'push', -} - -let noVerify: GitNoVerifyOption[] = [ - GitNoVerifyOption.Push, - GitNoVerifyOption.Commit, -]; +let noVerify: GitNoVerifyOption[] = ['push', 'commit']; export function setNoVerify(value: GitNoVerifyOption[]): void { if (!is.array(value, is.string)) { diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts index b2b27b4e31f8f7..1b4e1282193ee7 100644 --- a/lib/util/git/index.spec.ts +++ b/lib/util/git/index.spec.ts @@ -2,10 +2,10 @@ import fs from 'fs-extra'; import Git from 'simple-git'; import SimpleGit from 'simple-git/src/git'; import tmp from 'tmp-promise'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { CONFIG_VALIDATION } from '../../constants/error-messages'; import * as git from '.'; -import { GitNoVerifyOption, setNoVerify } from '.'; +import { setNoVerify } from '.'; describe('util/git/index', () => { jest.setTimeout(15000); @@ -71,7 +71,7 @@ describe('util/git/index', () => { await repo.clone(base.path, '.', ['--bare']); await repo.addConfig('commit.gpgsign', 'false'); tmpDir = await tmp.dir({ unsafeCleanup: true }); - setGlobalConfig({ localDir: tmpDir.path }); + GlobalConfig.set({ localDir: tmpDir.path }); await git.initRepo({ url: origin.path, }); @@ -94,6 +94,13 @@ describe('util/git/index', () => { await base.cleanup(); }); + describe('validateGitVersion()', () => { + it('has a git version greater or equal to the minimum required', async () => { + const res = await git.validateGitVersion(); + expect(res).toBeTrue(); + }); + }); + describe('checkoutBranch(branchName)', () => { it('sets the base branch as master', async () => { await expect(git.checkoutBranch(defaultBranch)).resolves.not.toThrow(); @@ -363,7 +370,7 @@ describe('util/git/index', () => { contents: 'some new-contents', }, ]; - setNoVerify([GitNoVerifyOption.Commit]); + setNoVerify(['commit']); await git.commitFiles({ branchName: 'renovate/something', @@ -393,7 +400,7 @@ describe('util/git/index', () => { contents: 'some new-contents', }, ]; - setNoVerify([GitNoVerifyOption.Push]); + setNoVerify(['push']); await git.commitFiles({ branchName: 'renovate/something', @@ -449,14 +456,14 @@ describe('util/git/index', () => { hostname: 'host', repository: 'some/repo', }) - ).toEqual('https://user:pass@host/some/repo.git'); + ).toBe('https://user:pass@host/some/repo.git'); expect( getUrl({ auth: 'user:pass', hostname: 'host', repository: 'some/repo', }) - ).toEqual('https://user:pass@host/some/repo.git'); + ).toBe('https://user:pass@host/some/repo.git'); }); it('returns ssh url', () => { @@ -467,7 +474,7 @@ describe('util/git/index', () => { hostname: 'host', repository: 'some/repo', }) - ).toEqual('git@host:some/repo.git'); + ).toBe('git@host:some/repo.git'); }); }); diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index 938bf1f31b26bc..c9130d17189973 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -1,16 +1,9 @@ import URL from 'url'; import fs from 'fs-extra'; -import Git, { - DiffResult as DiffResult_, - Options, - ResetMode, - SimpleGit, - StatusResult as StatusResult_, - TaskOptions, -} from 'simple-git'; +import Git, { Options, ResetMode, SimpleGit, TaskOptions } from 'simple-git'; import { join } from 'upath'; import { configFileNames } from '../../config/app-strings'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { CONFIG_VALIDATION, @@ -22,45 +15,28 @@ import { } from '../../constants/error-messages'; import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; -import { GitOptions, GitProtocol } from '../../types/git'; +import type { GitProtocol } from '../../types/git'; +import { api as semverCoerced } from '../../versioning/semver-coerced'; import { Limit, incLimitedValue } from '../../workers/global/limits'; import { regEx } from '../regex'; import { parseGitAuthor } from './author'; -import { GitNoVerifyOption, getNoVerify, simpleGitConfig } from './config'; +import { getNoVerify, simpleGitConfig } from './config'; import { configSigningKey, writePrivateKey } from './private-key'; - -export { GitNoVerifyOption, setNoVerify } from './config'; +import type { + CommitFilesConfig, + CommitSha, + LocalConfig, + StatusResult, + StorageConfig, +} from './types'; + +export { setNoVerify } from './config'; export { setPrivateKey } from './private-key'; declare module 'fs-extra' { export function exists(pathLike: string): Promise; } -export type StatusResult = StatusResult_; - -export type DiffResult = DiffResult_; - -export type CommitSha = string; - -interface StorageConfig { - currentBranch?: string; - url: string; - extraCloneOpts?: GitOptions; - cloneSubmodules?: boolean; - fullClone?: boolean; -} - -interface LocalConfig extends StorageConfig { - additionalBranches: string[]; - currentBranch: string; - currentBranchSha: string; - branchCommits: Record; - branchIsModified: Record; - ignoredAuthors: string[]; - gitAuthorName?: string; - gitAuthorEmail?: string; -} - // istanbul ignore next function checkForPlatformFailure(err: Error): void { if (process.env.NODE_ENV === 'test') { @@ -109,6 +85,11 @@ function checkForPlatformFailure(err: Error): void { message: 'You need the Git `GenericContribute` permission to perform this action', }, + { + error: 'matches more than one', + message: + "Renovate cannot push branches if there are tags with names the same as Renovate's branches. Please remove conflicting tag names or change Renovate's branchPrefix to avoid conflicts.", + }, ]; for (const { error, message } of configErrorStrings) { if (err.message.includes(error)) { @@ -163,6 +144,41 @@ let gitInitialized: boolean; let privateKeySet = false; +export const GIT_MINIMUM_VERSION = '2.33.0'; // git show-current + +export async function validateGitVersion(): Promise { + let version: string; + const globalGit = Git(); + try { + const raw = await globalGit.raw(['--version']); + for (const section of raw.split(/\s+/)) { + if (semverCoerced.isVersion(section)) { + version = section; + break; + } + } + } catch (err) /* istanbul ignore next */ { + logger.error({ err }, 'Error fetching git version'); + return false; + } + // istanbul ignore if + if ( + !( + version && + (version === GIT_MINIMUM_VERSION || + semverCoerced.isGreaterThan(version, GIT_MINIMUM_VERSION)) + ) + ) { + logger.error( + { detectedVersion: version, minimumVersion: GIT_MINIMUM_VERSION }, + 'Git version needs upgrading' + ); + return false; + } + logger.debug(`Found valid git version: ${version}`); + return true; +} + async function fetchBranchCommits(): Promise { config.branchCommits = {}; const opts = ['ls-remote', '--heads', config.url]; @@ -194,7 +210,7 @@ export async function initRepo(args: StorageConfig): Promise { config.ignoredAuthors = []; config.additionalBranches = []; config.branchIsModified = {}; - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); git = Git(localDir, simpleGitConfig()); gitInitialized = false; await fetchBranchCommits(); @@ -293,7 +309,7 @@ export async function syncGit(): Promise { return; } gitInitialized = true; - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); logger.debug('Initializing git repository into ' + localDir); const gitHead = join(localDir, '.git/HEAD'); let clone = true; @@ -655,33 +671,6 @@ export async function hasDiff(branchName: string): Promise { } } -/** - * File to commit - */ -export interface File { - /** - * Relative file path - */ - name: string; - - /** - * file contents - */ - contents: string | Buffer; - - /** - * the executable bit - */ - executable?: boolean; -} - -export type CommitFilesConfig = { - branchName: string; - files: File[]; - message: string; - force?: boolean; -}; - export async function commitFiles({ branchName, files, @@ -694,7 +683,7 @@ export async function commitFiles({ await writePrivateKey(); privateKeySet = true; } - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); await configSigningKey(localDir); await writeGitAuthor(); try { @@ -759,7 +748,7 @@ export async function commitFiles({ } const commitOptions: Options = {}; - if (getNoVerify().includes(GitNoVerifyOption.Commit)) { + if (getNoVerify().includes('commit')) { commitOptions['--no-verify'] = null; } @@ -790,7 +779,7 @@ export async function commitFiles({ '--force-with-lease': null, '-u': null, }; - if (getNoVerify().includes(GitNoVerifyOption.Push)) { + if (getNoVerify().includes('push')) { pushOptions['--no-verify'] = null; } diff --git a/lib/util/git/types.ts b/lib/util/git/types.ts new file mode 100644 index 00000000000000..edf2902e214fdd --- /dev/null +++ b/lib/util/git/types.ts @@ -0,0 +1,58 @@ +import type { GitOptions } from '../../types/git'; + +export type { DiffResult, StatusResult } from 'simple-git'; + +export interface GitAuthor { + name?: string; + address?: string; +} + +export type GitNoVerifyOption = 'commit' | 'push'; + +export type CommitSha = string; + +export interface StorageConfig { + currentBranch?: string; + url: string; + extraCloneOpts?: GitOptions; + cloneSubmodules?: boolean; + fullClone?: boolean; +} + +export interface LocalConfig extends StorageConfig { + additionalBranches: string[]; + currentBranch: string; + currentBranchSha: string; + branchCommits: Record; + branchIsModified: Record; + ignoredAuthors: string[]; + gitAuthorName?: string; + gitAuthorEmail?: string; +} + +/** + * File to commit + */ +export interface File { + /** + * Relative file path + */ + name: string; + + /** + * file contents + */ + contents: string | Buffer; + + /** + * the executable bit + */ + executable?: boolean; +} + +export type CommitFilesConfig = { + branchName: string; + files: File[]; + message: string; + force?: boolean; +}; diff --git a/lib/util/host-rules.spec.ts b/lib/util/host-rules.spec.ts index c60d24d9b51fea..9f64b19c6311ae 100644 --- a/lib/util/host-rules.spec.ts +++ b/lib/util/host-rules.spec.ts @@ -97,10 +97,10 @@ describe('util/host-rules', () => { expect( find({ hostType: datasourceNuget.id, url: 'https://api.github.com' }) .token - ).toEqual('def'); + ).toBe('def'); expect( find({ hostType: datasourceNuget.id, url: 'https://github.com' }).token - ).toEqual('def'); + ).toBe('def'); expect( find({ hostType: datasourceNuget.id, url: 'https://apigithub.com' }) .token @@ -131,7 +131,7 @@ describe('util/host-rules', () => { hostType: PlatformId.Github, url: 'https://api.github.com/repos/org-b/someRepo/tags?per_page=100', }).token - ).toEqual('def'); + ).toBe('def'); }); it('matches for several hostTypes when no hostType rule is configured', () => { @@ -144,13 +144,13 @@ describe('util/host-rules', () => { hostType: PlatformId.Github, url: 'https://api.github.com/repos/org-b/someRepo/tags?per_page=100', }).token - ).toEqual('abc'); + ).toBe('abc'); expect( find({ hostType: 'github-releases', url: 'https://api.github.com/repos/org-b/someRepo/tags?per_page=100', }).token - ).toEqual('abc'); + ).toBe('abc'); }); it('matches if hostType is configured and host rule is filtered with datasource', () => { @@ -169,7 +169,7 @@ describe('util/host-rules', () => { hostType: 'github-tags', url: 'https://api.github.com/repos/org-b/someRepo/tags?per_page=100', }).token - ).toEqual('def'); + ).toBe('def'); }); it('matches on hostName', () => { @@ -188,21 +188,21 @@ describe('util/host-rules', () => { token: 'def', }); expect(find({ url: 'https://api.domain.com' }).token).toBeUndefined(); - expect(find({ url: 'https://domain.com' }).token).toEqual('def'); + expect(find({ url: 'https://domain.com' }).token).toBe('def'); expect( find({ hostType: datasourceNuget.id, url: 'https://domain.com/renovatebot', }).token - ).toEqual('def'); + ).toBe('def'); }); it('matches on matchHost without protocol', () => { add({ matchHost: 'domain.com', token: 'def', }); - expect(find({ url: 'https://api.domain.com' }).token).toEqual('def'); - expect(find({ url: 'https://domain.com' }).token).toEqual('def'); + expect(find({ url: 'https://api.domain.com' }).token).toBe('def'); + expect(find({ url: 'https://domain.com' }).token).toBe('def'); expect(find({ url: 'httpsdomain.com' }).token).toBeUndefined(); }); it('matches on matchHost with dot prefix', () => { @@ -210,7 +210,7 @@ describe('util/host-rules', () => { matchHost: '.domain.com', token: 'def', }); - expect(find({ url: 'https://api.domain.com' }).token).toEqual('def'); + expect(find({ url: 'https://api.domain.com' }).token).toBe('def'); expect(find({ url: 'https://domain.com' }).token).toBeUndefined(); expect(find({ url: 'httpsdomain.com' }).token).toBeUndefined(); }); @@ -223,7 +223,7 @@ describe('util/host-rules', () => { expect( find({ hostType: datasourceNuget.id, url: 'https://nuget.local/api' }) .token - ).toEqual('abc'); + ).toBe('abc'); }); it('matches on endpoint subresource', () => { add({ diff --git a/lib/util/host-rules.ts b/lib/util/host-rules.ts index 298b9e76d413ee..5fbf389607149c 100644 --- a/lib/util/host-rules.ts +++ b/lib/util/host-rules.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import merge from 'deepmerge'; import { logger } from '../logger'; import { HostRule } from '../types'; @@ -7,27 +8,41 @@ import { parseUrl, validateUrl } from './url'; let hostRules: HostRule[] = []; -const legacyHostFields = ['hostName', 'domainName', 'baseUrl']; +interface LegacyHostRule { + hostName?: string; + domainName?: string; + baseUrl?: string; +} -export function add(params: HostRule): void { - const rule = clone(params); - const matchedFields = legacyHostFields.filter((field) => rule[field]); - if (matchedFields.length) { - if (rule.matchHost || matchedFields.length > 1) { - throw new Error( - `hostRules cannot contain more than one host-matching field - use "matchHost" only.` - ); - } - const field = matchedFields[0]; - logger.warn({ field }, 'Legacy hostRules field needs migrating'); - rule.matchHost = rule[field]; - delete rule[field]; +function migrateRule(rule: LegacyHostRule & HostRule): HostRule { + const cloned: LegacyHostRule & HostRule = clone(rule); + delete cloned.hostName; + delete cloned.domainName; + delete cloned.baseUrl; + const result: HostRule = cloned; + + const { matchHost } = result; + const { hostName, domainName, baseUrl } = rule; + const hostValues = [matchHost, hostName, domainName, baseUrl].filter(Boolean); + if (hostValues.length === 1) { + const [matchHost] = hostValues; + result.matchHost = matchHost; + } else if (hostValues.length > 1) { + throw new Error( + `hostRules cannot contain more than one host-matching field - use "matchHost" only.` + ); } - const confidentialFields = ['password', 'token']; + return result; +} + +export function add(params: HostRule): void { + const rule = migrateRule(params); + + const confidentialFields: (keyof HostRule)[] = ['password', 'token']; if (rule.matchHost) { const parsedUrl = parseUrl(rule.matchHost); - rule.resolvedHost = parsedUrl?.hostname || rule.matchHost; + rule.resolvedHost = parsedUrl?.hostname ?? rule.matchHost; confidentialFields.forEach((field) => { if (rule[field]) { logger.debug( @@ -38,7 +53,7 @@ export function add(params: HostRule): void { } confidentialFields.forEach((field) => { const secret = rule[field]; - if (secret && secret.length > 3) { + if (is.string(secret) && secret.length > 3) { sanitize.add(secret); } }); @@ -61,7 +76,7 @@ function isEmptyRule(rule: HostRule): boolean { } function isHostTypeRule(rule: HostRule): boolean { - return rule.hostType && !rule.resolvedHost; + return !!rule.hostType && !rule.resolvedHost; } function isHostOnlyRule(rule: HostRule): boolean { @@ -69,7 +84,7 @@ function isHostOnlyRule(rule: HostRule): boolean { } function isMultiRule(rule: HostRule): boolean { - return rule.hostType && !!rule.resolvedHost; + return !!rule.hostType && !!rule.resolvedHost; } function matchesHostType(rule: HostRule, search: HostRuleSearch): boolean { @@ -77,10 +92,14 @@ function matchesHostType(rule: HostRule, search: HostRuleSearch): boolean { } function matchesHost(rule: HostRule, search: HostRuleSearch): boolean { - if (validateUrl(rule.matchHost)) { + // istanbul ignore if + if (!rule.matchHost) { + return false; + } + if (search.url && validateUrl(rule.matchHost)) { return search.url.startsWith(rule.matchHost); } - const parsedUrl = parseUrl(search.url); + const parsedUrl = search.url ? parseUrl(search.url) : null; if (!parsedUrl?.hostname) { return false; } @@ -132,10 +151,13 @@ export function find(search: HostRuleSearch): HostRule { } export function hosts({ hostType }: { hostType: string }): string[] { - return hostRules - .filter((rule) => rule.hostType === hostType) - .map((rule) => rule.resolvedHost) - .filter(Boolean); + const result: string[] = []; + for (const rule of hostRules) { + if (rule.hostType === hostType && rule.resolvedHost) { + result.push(rule.resolvedHost); + } + } + return result; } export function findAll({ hostType }: { hostType: string }): HostRule[] { diff --git a/lib/util/http/auth.ts b/lib/util/http/auth.ts index 59dba806f71564..cd182af48d111f 100644 --- a/lib/util/http/auth.ts +++ b/lib/util/http/auth.ts @@ -86,14 +86,13 @@ export function removeAuthorization(options: NormalizedOptions): void { const portInUrl = options.href.split('/')[2].split(':')[1]; // istanbul ignore next if (!portInUrl) { - // eslint-disable-next-line no-param-reassign delete options.port; // Redirect will instead use 80 or 443 for HTTP or HTTPS respectively } // registry is hosted on Amazon or Azure blob, redirect url includes // authentication which is not required and should be removed - delete options.headers.authorization; // eslint-disable-line no-param-reassign - delete options.username; // eslint-disable-line no-param-reassign - delete options.password; // eslint-disable-line no-param-reassign + delete options.headers.authorization; + delete options.username; + delete options.password; } } diff --git a/lib/util/http/gitea.ts b/lib/util/http/gitea.ts index 2631c5e0d7ba71..110589be7d7fde 100644 --- a/lib/util/http/gitea.ts +++ b/lib/util/http/gitea.ts @@ -4,7 +4,7 @@ import { Http, HttpOptions, HttpResponse, InternalHttpOptions } from '.'; let baseUrl: string; export const setBaseUrl = (newBaseUrl: string): void => { - baseUrl = newBaseUrl.replace(/\/*$/, '/'); // TODO #12070 #12071 + baseUrl = newBaseUrl.replace(/\/*$/, '/'); // TODO #12875 }; export interface GiteaHttpOptions extends InternalHttpOptions { diff --git a/lib/util/http/github.spec.ts b/lib/util/http/github.spec.ts index c4043d3122bff5..9f9340264c4614 100644 --- a/lib/util/http/github.spec.ts +++ b/lib/util/http/github.spec.ts @@ -192,6 +192,14 @@ describe('util/http/github', () => { }) ).rejects.toThrow(PLATFORM_RATE_LIMIT_EXCEEDED); }); + it('should throw secondary rate limit exceeded', async () => { + await expect( + fail(403, { + message: + 'You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later.', + }) + ).rejects.toThrow(PLATFORM_RATE_LIMIT_EXCEEDED); + }); it('should throw Bad credentials', async () => { await expect( fail(401, { message: 'Bad credentials. (401)' }) @@ -356,7 +364,7 @@ describe('util/http/github', () => { await githubApi.requestGraphql(graphqlQuery); const [req] = httpMock.getTrace(); expect(req).toBeDefined(); - expect(req.url).toEqual('https://ghe.mycompany.com/api/graphql'); + expect(req.url).toBe('https://ghe.mycompany.com/api/graphql'); }); it('supports app mode', async () => { hostRules.add({ hostType: 'github', token: 'x-access-token:123test' }); diff --git a/lib/util/http/github.ts b/lib/util/http/github.ts index bdfd2f6f72507e..fa4bfbe04a87d5 100644 --- a/lib/util/http/github.ts +++ b/lib/util/http/github.ts @@ -15,7 +15,8 @@ import { regEx } from '../regex'; import { GotLegacyError } from './legacy'; import { Http, HttpPostOptions, HttpResponse, InternalHttpOptions } from '.'; -let baseUrl = 'https://api.github.com/'; +const githubBaseUrl = 'https://api.github.com/'; +let baseUrl = githubBaseUrl; export const setBaseUrl = (url: string): void => { baseUrl = url; }; @@ -55,11 +56,10 @@ function handleGotError( message = String(err.response.body.message); } if ( - err.name === 'RequestError' && - (err.code === 'ENOTFOUND' || - err.code === 'ETIMEDOUT' || - err.code === 'EAI_AGAIN' || - err.code === 'ECONNRESET') + err.code === 'ENOTFOUND' || + err.code === 'ETIMEDOUT' || + err.code === 'EAI_AGAIN' || + err.code === 'ECONNRESET' ) { logger.debug({ err }, 'GitHub failure: RequestError'); throw new ExternalHostError(err, PlatformId.Github); @@ -79,6 +79,13 @@ function handleGotError( logger.debug({ err }, 'GitHub failure: abuse detection'); throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED); } + if ( + err.statusCode === 403 && + message.startsWith('You have exceeded a secondary rate limit') + ) { + logger.debug({ err }, 'GitHub failure: secondary rate limit'); + throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED); + } if (err.statusCode === 403 && message.includes('Upgrade to GitHub Pro')) { logger.debug({ path }, 'Endpoint needs paid GitHub plan'); throw err; @@ -285,19 +292,9 @@ export class GithubHttp extends Http { result = res?.body; } catch (err) { logger.debug({ err, query, options }, 'Unexpected GraphQL Error'); - if (err instanceof ExternalHostError) { - const gotError = err.err as GotLegacyError; - const statusCode = gotError?.statusCode; - if ( - count && - count > 10 && - statusCode && - statusCode >= 500 && - statusCode < 600 - ) { - logger.info('Reducing pagination count to workaround graphql 5xx'); - return null; - } + if (err instanceof ExternalHostError && count && count > 10) { + logger.info('Reducing pagination count to workaround graphql errors'); + return null; } handleGotError(err, path, opts); } @@ -312,7 +309,10 @@ export class GithubHttp extends Http { const result: T[] = []; const { paginate = true } = options; - let count = options.count || 100; + + let optimalCount: null | number = null; + const initialCount = options.count || 100; + let count = initialCount; let limit = options.limit || 1000; let cursor: string = null; @@ -326,6 +326,8 @@ export class GithubHttp extends Http { }); const fieldData = res?.data?.repository?.[fieldName]; if (fieldData) { + optimalCount = count; + const { nodes = [], edges = [], pageInfo } = fieldData; result.push(...nodes); result.push(...edges); @@ -355,6 +357,19 @@ export class GithubHttp extends Http { } } + // See: https://github.com/renovatebot/renovate/issues/12703 + // istanbul ignore if + if ( + optimalCount && + optimalCount < initialCount && // log only shrinked results + baseUrl === githubBaseUrl + ) { + logger.debug( + { fieldName, optimalCount }, + 'Successful GraphQL query with shrinked pagination size' + ); + } + return result; } } diff --git a/lib/util/http/hooks.ts b/lib/util/http/hooks.ts new file mode 100644 index 00000000000000..2d6c571627d655 --- /dev/null +++ b/lib/util/http/hooks.ts @@ -0,0 +1,25 @@ +// Renovate issue: https://github.com/renovatebot/renovate/issues/12127 +// Got issue: https://github.com/sindresorhus/got/issues/1489 +// From here: https://github.com/sindresorhus/got/issues/1489#issuecomment-805485731 +import type { Hooks, Response } from 'got'; + +function isResponseOk(response: Response): boolean { + const { statusCode } = response; + const limitStatusCode = response.request.options.followRedirect ? 299 : 399; + + return ( + (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304 + ); +} + +export const hooks: Hooks = { + afterResponse: [ + (response: Response): Response => { + if (isResponseOk(response)) { + response.request.destroy(); + } + + return response; + }, + ], +}; diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts index 5a9c9f47cc9f23..b3003dfad468c2 100644 --- a/lib/util/http/index.spec.ts +++ b/lib/util/http/index.spec.ts @@ -213,6 +213,6 @@ describe('util/http/index', () => { httpMock.scope(baseUrl).get('/').reply(200, Buffer.from('test')); const res = await http.getBuffer('http://renovate.com'); expect(res.body).toBeInstanceOf(Buffer); - expect(res.body.toString('utf-8')).toEqual('test'); + expect(res.body.toString('utf-8')).toBe('test'); }); }); diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index d9c727ecab81f6..74f85816778772 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -8,6 +8,7 @@ import * as memCache from '../cache/memory'; import { clone } from '../clone'; import { resolveBaseUrl } from '../url'; import { applyAuthorization, removeAuthorization } from './auth'; +import { hooks } from './hooks'; import { applyHostRules } from './host-rules'; import { getQueue } from './queue'; import type { @@ -71,11 +72,11 @@ function applyDefaultHeaders(options: Options): void { let renovateVersion = 'unknown'; try { // eslint-disable-next-line @typescript-eslint/no-var-requires - renovateVersion = require('../../../package.json').version; // eslint-disable-line global-require + renovateVersion = require('../../../package.json').version; } catch (err) /* istanbul ignore next */ { logger.debug({ err }, 'Error getting renovate version'); } - // eslint-disable-next-line no-param-reassign + options.headers = { ...options.headers, 'user-agent': @@ -98,7 +99,7 @@ async function gotRoutine( // Cheat the TS compiler using `as` to pick a specific overload. // Otherwise it doesn't typecheck. - const resp = await got(url, options as GotJSONOptions); + const resp = await got(url, { ...options, hooks } as GotJSONOptions); const duration = resp.timings.phases.total || /* istanbul ignore next: can't be tested */ 0; @@ -292,13 +293,13 @@ export class Http { ...options, }; + let resolvedUrl = url; // istanbul ignore else: needs test if (options?.baseUrl) { - // eslint-disable-next-line no-param-reassign - url = resolveBaseUrl(options.baseUrl, url); + resolvedUrl = resolveBaseUrl(options.baseUrl, url); } applyDefaultHeaders(combinedOptions); - return got.stream(url, combinedOptions); + return got.stream(resolvedUrl, combinedOptions); } } diff --git a/lib/util/ignore.ts b/lib/util/ignore.ts index 6ef218d970996c..fc16a2e137446a 100644 --- a/lib/util/ignore.ts +++ b/lib/util/ignore.ts @@ -3,7 +3,6 @@ import { regEx } from './regex'; export function isSkipComment(comment?: string): boolean { if (regEx(/^(renovate|pyup):/).test(comment)) { - // TODO #12070 #12071 needs to be checked manually const command = comment.split('#')[0].split(':')[1].trim(); if (command === 'ignore') { return true; diff --git a/lib/util/index.ts b/lib/util/index.ts index 0d7c8fbf0f079a..dcd33b5ceda2d7 100644 --- a/lib/util/index.ts +++ b/lib/util/index.ts @@ -1,17 +1,17 @@ export function sampleSize(array: string[], n: number): string[] { - const length = array == null ? 0 : array.length; + const length = array ? array.length : 0; if (!length || n < 1) { return []; } - // eslint-disable-next-line no-param-reassign - n = n > length ? length : n; + + const sampleNumber = n > length ? length : n; let index = 0; const lastIndex = length - 1; const result = [...array]; - while (index < n) { + while (index < sampleNumber) { const rand = index + Math.floor(Math.random() * (lastIndex - index + 1)); [result[rand], result[index]] = [result[index], result[rand]]; index += 1; } - return result.slice(0, n); + return result.slice(0, sampleNumber); } diff --git a/lib/util/json-writer/editor-config.spec.ts b/lib/util/json-writer/editor-config.spec.ts index b3bbe22780585f..068028334d8ffa 100644 --- a/lib/util/json-writer/editor-config.spec.ts +++ b/lib/util/json-writer/editor-config.spec.ts @@ -1,7 +1,7 @@ import mockFs from 'mock-fs'; import { loadFixture } from '../../../test/util'; import { configFileNames } from '../../config/app-strings'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { EditorConfig } from './editor-config'; import { IndentationType } from './indentation-type'; @@ -13,7 +13,7 @@ const NON_JSON_FILES_EDITOR_CONFIG = loadFixture('.non_json_editorconfig', '.'); describe('util/json-writer/editor-config', () => { beforeAll(() => { - setGlobalConfig({ + GlobalConfig.set({ localDir: '', }); }); diff --git a/lib/util/json-writer/editor-config.ts b/lib/util/json-writer/editor-config.ts index 33bc638642fb33..f025d02d13670e 100644 --- a/lib/util/json-writer/editor-config.ts +++ b/lib/util/json-writer/editor-config.ts @@ -1,12 +1,12 @@ import { KnownProps, parse } from 'editorconfig'; import { join } from 'upath'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { CodeFormat } from './code-format'; import { IndentationType } from './indentation-type'; export class EditorConfig { public static async getCodeFormat(fileName: string): Promise { - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); const knownProps = await parse(join(localDir, fileName)); return { diff --git a/lib/util/json-writer/json-writer.spec.ts b/lib/util/json-writer/json-writer.spec.ts index aa7ea661c65afa..4e3f813810c9e0 100644 --- a/lib/util/json-writer/json-writer.spec.ts +++ b/lib/util/json-writer/json-writer.spec.ts @@ -9,7 +9,7 @@ describe('util/json-writer/json-writer', () => { it('should apply 2 spaces indentation by default', () => { const jsonWriter = new JSONWriter(); - expect(jsonWriter.write(DATA)).toStrictEqual('{\n "value": 1\n}\n'); + expect(jsonWriter.write(DATA)).toBe('{\n "value": 1\n}\n'); }); it('should apply indentation size', () => { @@ -18,9 +18,7 @@ describe('util/json-writer/json-writer', () => { indentationSize: 10, }); - expect(jsonWriter.write(DATA)).toStrictEqual( - '{\n "value": 1\n}\n' - ); + expect(jsonWriter.write(DATA)).toBe('{\n "value": 1\n}\n'); }); it('should apply indentation type', () => { @@ -28,7 +26,7 @@ describe('util/json-writer/json-writer', () => { indentationType: IndentationType.Tab, }); - expect(jsonWriter.write(DATA)).toStrictEqual('{\n\t"value": 1\n}\n'); + expect(jsonWriter.write(DATA)).toBe('{\n\t"value": 1\n}\n'); }); it('new line at the end should be optional', () => { @@ -37,8 +35,6 @@ describe('util/json-writer/json-writer', () => { indentationSize: 10, }); - expect(jsonWriter.write(DATA, false)).toStrictEqual( - '{\n "value": 1\n}' - ); + expect(jsonWriter.write(DATA, false)).toBe('{\n "value": 1\n}'); }); }); diff --git a/lib/util/mask.spec.ts b/lib/util/mask.spec.ts index 7d0753ba9604d2..e7cdb0816c5464 100644 --- a/lib/util/mask.spec.ts +++ b/lib/util/mask.spec.ts @@ -2,12 +2,13 @@ import { maskToken } from './mask'; describe('util/mask', () => { describe('.maskToken', () => { - it('returns value if passed value is falsy', () => { - expect(maskToken('')).toEqual(''); + it('returns empty string if passed value is falsy', () => { + expect(maskToken()).toBe(''); + expect(maskToken('')).toBe(''); }); it('hides value content', () => { - expect(maskToken('123456789')).toEqual('12*****89'); + expect(maskToken('123456789')).toBe('12*****89'); }); }); }); diff --git a/lib/util/mask.ts b/lib/util/mask.ts index dd50d4336628d4..83fa009a351b00 100644 --- a/lib/util/mask.ts +++ b/lib/util/mask.ts @@ -5,5 +5,5 @@ export function maskToken(str?: string): string { new Array(str.length - 3).join('*'), str.slice(-2), ].join('') - : str; + : ''; } diff --git a/lib/util/merge-confidence/index.ts b/lib/util/merge-confidence/index.ts index f259bb13eaaa78..ec29993be7b68c 100644 --- a/lib/util/merge-confidence/index.ts +++ b/lib/util/merge-confidence/index.ts @@ -36,6 +36,7 @@ const updateTypeConfidenceMapping: Record = { lockFileMaintenance: 'neutral', lockfileUpdate: 'neutral', rollback: 'neutral', + replacement: 'neutral', major: null, minor: null, patch: null, diff --git a/lib/util/modules.ts b/lib/util/modules.ts index 9d988ac1095be3..5b3b9fc27d44c4 100644 --- a/lib/util/modules.ts +++ b/lib/util/modules.ts @@ -3,8 +3,8 @@ import { join, normalizeTrim } from 'upath'; import { regEx } from './regex'; function relatePath(here: string, there: string): string { - const thereParts = normalizeTrim(there).split(regEx(/[\\/]/)); // TODO #12070 needs to be tested manually - const hereParts = normalizeTrim(here).split(regEx(/[\\/]/)); // TODO #12070 needs to be tested manually + const thereParts = normalizeTrim(there).split(regEx(/[\\/]/)); + const hereParts = normalizeTrim(here).split(regEx(/[\\/]/)); let idx = 0; while ( diff --git a/lib/util/package-rules.spec.ts b/lib/util/package-rules.spec.ts index 79647e1c675ab2..39cce745fb9cc8 100644 --- a/lib/util/package-rules.spec.ts +++ b/lib/util/package-rules.spec.ts @@ -731,6 +731,27 @@ describe('util/package-rules', () => { ], }; const res = applyPackageRules(config); - expect(res.groupSlug).toEqual('b'); + expect(res.groupSlug).toBe('b'); + }); + it('matches matchSourceUrlPrefixes(case-insensitive)', () => { + const config: TestConfig = { + packageRules: [ + { + matchSourceUrlPrefixes: [ + 'https://github.com/foo/bar', + 'https://github.com/Renovatebot/', + ], + x: 1, + }, + ], + }; + const dep = { + depType: 'dependencies', + depName: 'a', + updateType: 'patch' as UpdateType, + sourceUrl: 'https://github.com/renovatebot/Presets', + }; + const res = applyPackageRules({ ...config, ...dep }); + expect(res.x).toBe(1); }); }); diff --git a/lib/util/package-rules.ts b/lib/util/package-rules.ts index 2f21b91bcd611f..487738be03a722 100644 --- a/lib/util/package-rules.ts +++ b/lib/util/package-rules.ts @@ -5,7 +5,7 @@ import { mergeChildConfig } from '../config'; import type { PackageRule, PackageRuleInputConfig } from '../config/types'; import { logger } from '../logger'; import * as allVersioning from '../versioning'; -import { configRegexPredicate, isConfigRegex, regEx } from './regex'; +import { configRegexPredicate, regEx } from './regex'; function matchesRule( inputConfig: PackageRuleInputConfig, @@ -29,6 +29,7 @@ function matchesRule( manager, datasource, } = inputConfig; + const unconstrainedValue = lockedVersion && is.undefined(currentValue); // Setting empty arrays simplifies our logic later const matchFiles = packageRule.matchFiles || []; const matchPaths = packageRule.matchPaths || []; @@ -193,8 +194,9 @@ function matchesRule( positiveMatch = true; } if (matchSourceUrlPrefixes.length) { + const upperCaseSourceUrl = sourceUrl?.toUpperCase(); const isMatch = matchSourceUrlPrefixes.some((prefix) => - sourceUrl?.startsWith(prefix) + upperCaseSourceUrl?.startsWith(prefix.toUpperCase()) ); if (!isMatch) { return false; @@ -204,16 +206,20 @@ function matchesRule( if (matchCurrentVersion) { const version = allVersioning.get(versioning); const matchCurrentVersionStr = matchCurrentVersion.toString(); - if (isConfigRegex(matchCurrentVersionStr)) { - const matches = configRegexPredicate(matchCurrentVersionStr); - if (!matches(currentValue)) { + const matchCurrentVersionPred = configRegexPredicate( + matchCurrentVersionStr + ); + if (matchCurrentVersionPred) { + if (!unconstrainedValue && !matchCurrentVersionPred(currentValue)) { return false; } positiveMatch = true; } else if (version.isVersion(matchCurrentVersionStr)) { let isMatch = false; try { - isMatch = version.matches(matchCurrentVersionStr, currentValue); + isMatch = + unconstrainedValue || + version.matches(matchCurrentVersionStr, currentValue); } catch (err) { // Do nothing } diff --git a/lib/util/regex.spec.ts b/lib/util/regex.spec.ts index b0dad6a8b310e9..49951096c33cd9 100644 --- a/lib/util/regex.spec.ts +++ b/lib/util/regex.spec.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-extraneous-dependencies import RE2 from 're2'; import { CONFIG_VALIDATION } from '../constants/error-messages'; import { regEx } from './regex'; diff --git a/lib/util/regex.ts b/lib/util/regex.ts index 19b203ea512046..a59bf7b0ccf892 100644 --- a/lib/util/regex.ts +++ b/lib/util/regex.ts @@ -1,10 +1,12 @@ import is from '@sindresorhus/is'; import { CONFIG_VALIDATION } from '../constants/error-messages'; -// eslint-disable-next-line import/no-cycle + import { logger } from '../logger'; let RegEx: RegExpConstructor; +const cache = new Map(); + try { // eslint-disable-next-line const RE2 = require('re2'); @@ -18,8 +20,17 @@ try { } export function regEx(pattern: string | RegExp, flags?: string): RegExp { + const key = `${pattern.toString()}:${flags}`; + + const cachedResult = cache.get(key); + if (cachedResult) { + return cachedResult; + } + try { - return new RegEx(pattern, flags); + const instance = new RegEx(pattern, flags); + cache.set(key, instance); + return instance; } catch (err) { const error = new Error(CONFIG_VALIDATION); error.validationSource = pattern.toString(); @@ -53,16 +64,20 @@ function parseConfigRegex(input: string): RegExp | null { return null; } -type ConfigRegexPredicate = (string) => boolean; +type ConfigRegexPredicate = (s: string) => boolean; -export function configRegexPredicate(input: string): ConfigRegexPredicate { - const configRegex = parseConfigRegex(input); - if (configRegex) { - const isPositive = !input.startsWith('!'); - return (x: string): boolean => { - const res = configRegex.test(x); - return isPositive ? res : !res; - }; +export function configRegexPredicate( + input: string +): ConfigRegexPredicate | null { + if (isConfigRegex(input)) { + const configRegex = parseConfigRegex(input); + if (configRegex) { + const isPositive = !input.startsWith('!'); + return (x: string): boolean => { + const res = configRegex.test(x); + return isPositive ? res : !res; + }; + } } return null; } diff --git a/lib/util/sanitize.spec.ts b/lib/util/sanitize.spec.ts index c3d96fb9d59fee..15068a9d9db91b 100644 --- a/lib/util/sanitize.spec.ts +++ b/lib/util/sanitize.spec.ts @@ -16,11 +16,14 @@ describe('util/sanitize', () => { const hashed = Buffer.from(`${username}:${password}`).toString('base64'); add(hashed); add(password); - // FIXME: explicit assert condition - expect( - sanitize( - `My token is ${token}, username is "${username}" and password is "${password}" (hashed: ${hashed})` - ) - ).toMatchSnapshot(); + + const input = `My token is ${token}, username is "${username}" and password is "${password}" (hashed: ${hashed})`; + const output = + 'My token is **redacted**, username is "userabc" and password is "**redacted**" (hashed: **redacted**)'; + expect(sanitize(input)).toBe(output); + + const inputX2 = [input, input].join('\n'); + const outputX2 = [output, output].join('\n'); + expect(sanitize(inputX2)).toBe(outputX2); }); }); diff --git a/lib/util/template/__snapshots__/index.spec.ts.snap b/lib/util/template/__snapshots__/index.spec.ts.snap index e9fc7048944be9..d900a0a7515238 100644 --- a/lib/util/template/__snapshots__/index.spec.ts.snap +++ b/lib/util/template/__snapshots__/index.spec.ts.snap @@ -1,3 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`util/template/index filters out disallowed fields 1`] = `"github token = \\"\\""`; + +exports[`util/template/index string to pretty JSON 1`] = ` +"{ + \\"some\\": { + \\"fancy\\": \\"json\\" + } +}" +`; diff --git a/lib/util/template/index.spec.ts b/lib/util/template/index.spec.ts index 652d0f5e352757..55ab3c7b27362c 100644 --- a/lib/util/template/index.spec.ts +++ b/lib/util/template/index.spec.ts @@ -18,4 +18,10 @@ describe('util/template/index', () => { expect(output).toContain('github'); expect(output).not.toContain('123test'); }); + it('string to pretty JSON ', () => { + const userTemplate = + '{{{ stringToPrettyJSON \'{"some":{"fancy":"json"}}\'}}}'; + const output = template.compile(userTemplate, undefined); + expect(output).toMatchSnapshot(); + }); }); diff --git a/lib/util/template/index.ts b/lib/util/template/index.ts index cb61a66b105587..a449097b3f8ac7 100644 --- a/lib/util/template/index.ts +++ b/lib/util/template/index.ts @@ -1,15 +1,19 @@ import is from '@sindresorhus/is'; import * as handlebars from 'handlebars'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import { clone } from '../clone'; handlebars.registerHelper('encodeURIComponent', encodeURIComponent); +handlebars.registerHelper('stringToPrettyJSON', (input: string): string => + JSON.stringify(JSON.parse(input), null, 2) +); + // istanbul ignore next handlebars.registerHelper( 'replace', - (find, replace, context) => context.replace(new RegExp(find, 'g'), replace) // TODO #12070 + (find, replace, context) => context.replace(new RegExp(find, 'g'), replace) // TODO #12873 ); export const exposedConfigOptions = [ @@ -60,6 +64,7 @@ export const allowedFields = { isPatch: 'true if the upgrade is a patch upgrade', isPin: 'true if the upgrade is pinning dependencies', isRollback: 'true if the upgrade is a rollback PR', + isReplacement: 'true if the upgrade is a replacement', isRange: 'true if the new value is a range', isSingleVersion: 'true if the upgrade is to a single version rather than a range', @@ -72,6 +77,8 @@ export const allowedFields = { 'The major version of the new version. e.g. "3" if the new version if "3.1.0"', newMinor: 'The minor version of the new version. e.g. "1" if the new version if "3.1.0"', + newName: + 'The name of the new dependency that replaces the current deprecated dependency', newValue: 'The new value in the upgrade. Can be a range or version e.g. "^3.0.0" or "3.1.0"', newVersion: 'The new version in the upgrade, e.g. "3.1.0"', @@ -91,7 +98,7 @@ export const allowedFields = { semanticPrefix: 'The fully generated semantic prefix for commit messages', sourceRepoSlug: 'The slugified pathname of the sourceUrl, if present', sourceUrl: 'The source URL for the package', - updateType: 'One of digest, pin, rollback, patch, minor, major', + updateType: 'One of digest, pin, rollback, patch, minor, major, replacement', upgrades: 'An array of upgrade objects in the branch', url: 'The url of the release notes', version: 'The version number of the changelog', @@ -140,14 +147,14 @@ function getFilteredObject(input: CompileInput): any { return res; } -const templateRegex = /{{(#(if|unless) )?([a-zA-Z]+)}}/g; // TODO #12070 +const templateRegex = /{{(#(if|unless) )?([a-zA-Z]+)}}/g; // TODO #12873 export function compile( template: string, input: CompileInput, filterFields = true ): string { - const data = { ...getGlobalConfig(), ...input }; + const data = { ...GlobalConfig.get(), ...input }; const filteredInput = filterFields ? getFilteredObject(data) : data; logger.trace({ template, filteredInput }, 'Compiling template'); if (filterFields) { diff --git a/lib/util/url.ts b/lib/util/url.ts index 4b3ac3362eefc0..48ca4fdf141f91 100644 --- a/lib/util/url.ts +++ b/lib/util/url.ts @@ -15,11 +15,11 @@ export function ensurePathPrefix(url: string, prefix: string): string { } export function ensureTrailingSlash(url: string): string { - return url.replace(/\/?$/, '/'); // TODO #12070 #12071 add tests for this one + return url.replace(/\/?$/, '/'); // TODO #12875 adds slash at the front when re2 is used } export function trimTrailingSlash(url: string): string { - return url.replace(regEx(/\/+$/), ''); // TODO #12071 + return url.replace(regEx(/\/+$/), ''); } export function resolveBaseUrl(baseUrl: string, input: string | URL): string { @@ -77,5 +77,5 @@ export function parseUrl(url: string): URL | null { * @returns an URL object or null */ export function createURLFromHostOrURL(url: string): URL | null { - return parseUrl(url) || parseUrl(`https://${url}`); + return parseUrl(url) ?? parseUrl(`https://${url}`); } diff --git a/lib/versioning/api.ts b/lib/versioning/api.ts index a6770f7794ab94..932441370655fa 100644 --- a/lib/versioning/api.ts +++ b/lib/versioning/api.ts @@ -1,3 +1,4 @@ +import * as amazonMachineImage from './aws-machine-image'; import * as cargo from './cargo'; import * as composer from './composer'; import * as docker from './docker'; @@ -26,6 +27,7 @@ import * as ubuntu from './ubuntu'; const api = new Map(); export default api; +api.set(amazonMachineImage.id, amazonMachineImage.api); api.set('cargo', cargo.api); api.set('composer', composer.api); api.set('docker', docker.api); diff --git a/lib/versioning/aws-machine-image/index.spec.ts b/lib/versioning/aws-machine-image/index.spec.ts new file mode 100644 index 00000000000000..e4aff0a818fdc7 --- /dev/null +++ b/lib/versioning/aws-machine-image/index.spec.ts @@ -0,0 +1,50 @@ +import aws from '.'; + +describe('versioning/aws-machine-image/index', () => { + describe('parse(version)', () => { + it('should return 1.0.0', () => { + expect(aws.getMajor('ami-00e1b2c30011d4e5f')).toBe(1); + expect(aws.getMinor('ami-00e1b2c30011d4e5f')).toBe(0); + expect(aws.getPatch('ami-00e1b2c30011d4e5f')).toBe(0); + }); + }); + describe('isValid(version)', () => { + it('should return true', () => { + expect(aws.isValid('ami-00e1b2c30011d4e5f')).toBeTruthy(); + }); + it('should return false', () => { + expect(aws.isValid('ami-1')).toBeFalsy(); + }); + }); + describe('isVersion(version)', () => { + it('should return true', () => { + expect(aws.isVersion('ami-00e1b2c30011d4e5f')).toBeTruthy(); + }); + it('should return false', () => { + expect(aws.isVersion('ami-1')).toBeFalsy(); + }); + }); + describe('isCompatible(version)', () => { + it('should return true', () => { + expect(aws.isCompatible('ami-00e1b2c30011d4e5f')).toBeTruthy(); + }); + it('should return false', () => { + expect(aws.isCompatible('ami-1')).toBeFalsy(); + }); + }); + describe('isCompatible(version,range)', () => { + it('should return true', () => { + expect( + aws.isCompatible('ami-00e1b2c30011d4e5f', 'anything') + ).toBeTruthy(); + }); + it('should return false', () => { + expect(aws.isCompatible('ami-1', 'anything')).toBeFalsy(); + }); + }); + describe('isGreaterThan(version1, version2)', () => { + it('should return false', () => { + expect(aws.isGreaterThan('', '')).toBeTruthy(); + }); + }); +}); diff --git a/lib/versioning/aws-machine-image/index.ts b/lib/versioning/aws-machine-image/index.ts new file mode 100644 index 00000000000000..0f7b6fed1b8ec2 --- /dev/null +++ b/lib/versioning/aws-machine-image/index.ts @@ -0,0 +1,44 @@ +import * as generic from '../loose/generic'; +import type { VersioningApi } from '../types'; + +export const id = 'aws-machine-image'; +export const displayName = 'aws-machine-image'; + +export const urls = []; + +export const supportsRanges = false; + +function parse(version: string): any { + return { release: [1, 0, 0] }; +} + +function compare(version1: string, version2: string): number { + return 1; +} + +function isValid(input: string): string | boolean | null { + return typeof input === 'string' && /^ami-[a-z0-9]{17}$/.test(input); +} + +function isVersion(input: string): string | boolean | null { + return isValid(input); +} + +function isCompatible( + version: string, + _range?: string +): string | boolean | null { + return isValid(version); +} + +export const api: VersioningApi = { + ...generic.create({ + parse, + compare, + }), + isValid, + isVersion, + isCompatible, +}; + +export default api; diff --git a/lib/versioning/aws-machine-image/readme.md b/lib/versioning/aws-machine-image/readme.md new file mode 100644 index 00000000000000..5c12ed0f305451 --- /dev/null +++ b/lib/versioning/aws-machine-image/readme.md @@ -0,0 +1,3 @@ +Renovate's AWS Machine Image versioning is a kind of hack to support Amazon Machine Images (AMI) updates. + +At the moment every AMI that matches the regex `^ami-[a-z0-9]{17}$` is considered a valid "release". diff --git a/lib/versioning/git/index.spec.ts b/lib/versioning/git/index.spec.ts index 41d2a764cca376..53b5b8b35c9e4a 100644 --- a/lib/versioning/git/index.spec.ts +++ b/lib/versioning/git/index.spec.ts @@ -18,7 +18,7 @@ describe('versioning/git/index', () => { }); describe('valueToVersion(version)', () => { it('should return same as input', () => { - expect(git.valueToVersion('')).toEqual(''); + expect(git.valueToVersion('')).toBe(''); }); }); }); diff --git a/lib/versioning/gradle/index.spec.ts b/lib/versioning/gradle/index.spec.ts index d67f436dacd951..903db8b72c4ffa 100644 --- a/lib/versioning/gradle/index.spec.ts +++ b/lib/versioning/gradle/index.spec.ts @@ -19,6 +19,7 @@ describe('versioning/gradle/index', () => { ${'1.a-1'} | ${'1a1'} | ${0} ${'dev'} | ${'dev'} | ${0} ${'rc'} | ${'rc'} | ${0} + ${'preview'} | ${'preview'} | ${0} ${'release'} | ${'release'} | ${0} ${'final'} | ${'final'} | ${0} ${'snapshot'} | ${'SNAPSHOT'} | ${0} @@ -43,6 +44,7 @@ describe('versioning/gradle/index', () => { ${'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} @@ -68,6 +70,7 @@ describe('versioning/gradle/index', () => { ${'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} @@ -170,6 +173,7 @@ describe('versioning/gradle/index', () => { ${'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} diff --git a/lib/versioning/gradle/index.ts b/lib/versioning/gradle/index.ts index 990f3d7ff7314f..fa60dc22c520c1 100644 --- a/lib/versioning/gradle/index.ts +++ b/lib/versioning/gradle/index.ts @@ -24,7 +24,7 @@ const equals = (a: string, b: string): boolean => compare(a, b) === 0; const getMajor = (version: string): number | null => { if (isVersion(version)) { const tokens = tokenize(version.replace(regEx(/^v/i), '')); - const majorToken = tokens[0]; + const majorToken = tokens?.[0]; if (majorToken && majorToken.type === TokenType.Number) { return +majorToken.val; } @@ -83,6 +83,7 @@ const unstable = new Set([ 'milestone', 'rc', 'cr', + 'preview', 'snapshot', ]); diff --git a/lib/versioning/hashicorp/index.ts b/lib/versioning/hashicorp/index.ts index 79749443b08a0e..62c1e99f476ffb 100644 --- a/lib/versioning/hashicorp/index.ts +++ b/lib/versioning/hashicorp/index.ts @@ -49,14 +49,14 @@ function getNewValue({ replaceValue = `$${npm.getMinor(newVersion)}$`; } return currentValue.replace( - /(?~>\s*0\.)\d+(?.*)$/, // TODO #12070 + regEx(`(?~>\\s*0\\.)\\d+(?.*)$`), // TODO #12071 replaceValue ); } // handle special ~> 1.2 case if (regEx(/(~>\s*)\d+\.\d+$/).test(currentValue)) { return currentValue.replace( - /(?~>\s*)\d+\.\d+$/, // TODO #12070 + regEx(`(?~>\\s*)\\d+\\.\\d+$`), // TODO #12071 `$${npm.getMajor(newVersion)}.0` ); } diff --git a/lib/versioning/index.spec.ts b/lib/versioning/index.spec.ts index 87f63c9526bb52..da1137ccac1e0a 100644 --- a/lib/versioning/index.spec.ts +++ b/lib/versioning/index.spec.ts @@ -20,7 +20,6 @@ describe('versioning/index', () => { module: VersioningApi | VersioningApiConstructor, name: string ): boolean { - // eslint-disable-next-line new-cap const mod = isVersioningApiConstructor(module) ? new module() : module; // TODO: test required api (#9715) @@ -81,7 +80,6 @@ describe('versioning/index', () => { props.push(prop); } }); - // eslint-disable-next-line no-cond-assign } while ((o = Object.getPrototypeOf(o))); return props; @@ -112,12 +110,10 @@ describe('versioning/index', () => { it('dummy', () => { class DummyScheme extends GenericVersioningApi { - // eslint-disable-next-line class-methods-use-this protected override _compare(_version: string, _other: string): number { throw new Error('Method not implemented.'); } - // eslint-disable-next-line class-methods-use-this protected _parse(_version: string): GenericVersion { throw new Error('Method not implemented.'); } diff --git a/lib/versioning/index.ts b/lib/versioning/index.ts index a192f8353833cb..0bdc0e7d6c15a2 100644 --- a/lib/versioning/index.ts +++ b/lib/versioning/index.ts @@ -35,7 +35,6 @@ export function get(versioning: string): VersioningApi { return versionings.get('semver') as VersioningApi; } if (isVersioningApiConstructor(theVersioning)) { - // eslint-disable-next-line new-cap return new theVersioning(versioningConfig); } return theVersioning; diff --git a/lib/versioning/loose/generic.ts b/lib/versioning/loose/generic.ts index 55646f2b704d52..609adec8c14409 100644 --- a/lib/versioning/loose/generic.ts +++ b/lib/versioning/loose/generic.ts @@ -175,7 +175,7 @@ export abstract class GenericVersioningApi< /* * virtual */ - // eslint-disable-next-line class-methods-use-this + protected _compareOther(_left: T, _right: T): number { return 0; } @@ -235,7 +235,6 @@ export abstract class GenericVersioningApi< return versions.find((v) => this.equals(v, range)) || null; } - // eslint-disable-next-line class-methods-use-this getNewValue(newValueConfig: NewValueConfig): string { const { newVersion } = newValueConfig || {}; return newVersion; diff --git a/lib/versioning/loose/utils.spec.ts b/lib/versioning/loose/utils.spec.ts index 0a2e2652ab98b3..4b503762d0c998 100644 --- a/lib/versioning/loose/utils.spec.ts +++ b/lib/versioning/loose/utils.spec.ts @@ -23,7 +23,6 @@ describe('versioning/loose/utils', () => { props.push(prop); } }); - // eslint-disable-next-line no-cond-assign } while ((o = Object.getPrototypeOf(o))); return props; @@ -31,12 +30,10 @@ describe('versioning/loose/utils', () => { describe('GenericVersioningApi', () => { class DummyScheme extends GenericVersioningApi { - // eslint-disable-next-line class-methods-use-this protected override _compare(_version: string, _other: string): number { return _version ? _version.localeCompare(_other) : 0; } - // eslint-disable-next-line class-methods-use-this protected _parse(_version: string): GenericVersion { return _version === 'test' ? null : { release: [1, 0, 0] }; } diff --git a/lib/versioning/pep440/range.ts b/lib/versioning/pep440/range.ts index 9e7785a9e09c85..53e00980fbc833 100644 --- a/lib/versioning/pep440/range.ts +++ b/lib/versioning/pep440/range.ts @@ -80,7 +80,10 @@ export function getNewValue({ } if (ranges.some((range) => range.operator === '===')) { // the operator "===" is used for legacy non PEP440 versions - logger.warn('Arbitrary equality not supported: ' + currentValue); + logger.warn( + { currentValue }, + 'PEP440 arbitrary equality (===) not supported' + ); return null; } let result = ranges diff --git a/lib/versioning/regex/index.ts b/lib/versioning/regex/index.ts index f597df18a8da16..b5db3258097814 100644 --- a/lib/versioning/regex/index.ts +++ b/lib/versioning/regex/index.ts @@ -41,12 +41,9 @@ export class RegExpVersioningApi extends GenericVersioningApi { // RegExp('^(?\\d+)\\.(?\\d+)\\.(?\\d+)(:?-(?.*-r)(?\\d+))?$'); private _config: RegExp = null; - constructor(new_config: string) { + constructor(_new_config: string) { super(); - if (!new_config) { - // eslint-disable-next-line no-param-reassign - new_config = '^(?\\d+)?$'; - } + const new_config = _new_config || '^(?\\d+)?$'; // without at least one of {major, minor, patch} specified in the regex, // this versioner will not work properly diff --git a/lib/versioning/rez/pattern.ts b/lib/versioning/rez/pattern.ts index d2d7c98e46d39a..432da270404bf7 100644 --- a/lib/versioning/rez/pattern.ts +++ b/lib/versioning/rez/pattern.ts @@ -76,17 +76,17 @@ export const inclusiveBound = regEx( `^(?(?${versionGroup})?\\.\\.(?${versionGroup})?)$` ); /* Match an inclusive bound (e.g. 1.0.0..2.0.0) */ // Add ? after |\\+) in order to match >=1.15 -export const lowerBound = new RegExp( // TODO #12070 +export const lowerBound = new RegExp( // TODO #12872 named backreference `^(?(?>|>=)?(?${versionGroup})?(\\k|\\+)?)$` ); /* Match a lower bound (e.g. 1.0.0+) */ -export const upperBound = new RegExp( // TODO #12070 +export const upperBound = new RegExp( // TODO #12872 lookahead `^(?(?<(?=${versionGroup})|<=)?(?${versionGroup})?)$` ); /* Match an upper bound (e.g. <=1.0.0) */ // Add ,? to match >=7,<9 (otherwise it just matches >=7<9) -export const ascendingRange = new RegExp( // TODO #12070 +export const ascendingRange = new RegExp( // TODO #12872 named backreference `^(?(?(?>|>=)?(?${versionGroup})?(\\k|\\+)?),?(?(\\k,?|)(?<(?=${versionGroup})|<=)(?${versionGroup})?))$` ); /* Match a range in ascending order (e.g. 1.0.0+<2.0.0) */ // Add , to match <9,>=7 (otherwise it just matches <9>=7) -export const descendingRange = new RegExp( // TODO #12070 +export const descendingRange = new RegExp( // TODO #12872 named backreference `^(?(?(?<|<=)?(?${versionGroup})?(\\k|\\+)?),(?(\\k,|)(?<(?=${versionGroup})|>=?)(?${versionGroup})?))$` ); /* Match a range in descending order (e.g. <=2.0.0,1.0.0+) */ diff --git a/lib/versioning/ruby/index.ts b/lib/versioning/ruby/index.ts index 929e267ae90ff0..ce03799f00d6f4 100644 --- a/lib/versioning/ruby/index.ts +++ b/lib/versioning/ruby/index.ts @@ -129,11 +129,14 @@ const getNewValue = ({ .split(',') .map( (element) => - element.replace(/^(?\s*)/, `$${delimiter}`) // TODO #12071 #12070 + element.replace( + regEx(`^(?\\s*)`), + `$${delimiter}` + ) // TODO #12071 ) .map( (element) => - element.replace(/(?\s*)$/, `${delimiter}$`) // TODO #12071 #12070 + element.replace(/(?\s*)$/, `${delimiter}$`) // TODO #12071 #12875 adds ' at front when re2 is used ) .join(','); } diff --git a/lib/versioning/semver-coerced/index.spec.ts b/lib/versioning/semver-coerced/index.spec.ts index c0ec30e40297b1..25fdee12ad445f 100644 --- a/lib/versioning/semver-coerced/index.spec.ts +++ b/lib/versioning/semver-coerced/index.spec.ts @@ -18,31 +18,31 @@ describe('versioning/semver-coerced/index', () => { describe('.getMajor(input)', () => { it('should return major version number for strict semver', () => { - expect(semverCoerced.getMajor('1.0.2')).toEqual(1); + expect(semverCoerced.getMajor('1.0.2')).toBe(1); }); it('should return major version number for non-strict semver', () => { - expect(semverCoerced.getMajor('v3.1')).toEqual(3); + expect(semverCoerced.getMajor('v3.1')).toBe(3); }); }); describe('.getMinor(input)', () => { it('should return minor version number for strict semver', () => { - expect(semverCoerced.getMinor('1.0.2')).toEqual(0); + expect(semverCoerced.getMinor('1.0.2')).toBe(0); }); it('should return minor version number for non-strict semver', () => { - expect(semverCoerced.getMinor('v3.1')).toEqual(1); + expect(semverCoerced.getMinor('v3.1')).toBe(1); }); }); describe('.getPatch(input)', () => { it('should return patch version number for strict semver', () => { - expect(semverCoerced.getPatch('1.0.2')).toEqual(2); + expect(semverCoerced.getPatch('1.0.2')).toBe(2); }); it('should return patch version number for non-strict semver', () => { - expect(semverCoerced.getPatch('v3.1.2-foo')).toEqual(2); + expect(semverCoerced.getPatch('v3.1.2-foo')).toBe(2); }); }); @@ -174,13 +174,13 @@ describe('versioning/semver-coerced/index', () => { it('should return max satisfying version in range', () => { expect( semverCoerced.getSatisfyingVersion(['1.0.0', '1.0.4'], '^1.0') - ).toEqual('1.0.4'); + ).toBe('1.0.4'); }); it('should support coercion', () => { expect( semverCoerced.getSatisfyingVersion(['v1.0', '1.0.4-foo'], '^1.0') - ).toEqual('1.0.4'); + ).toBe('1.0.4'); }); }); @@ -188,13 +188,13 @@ describe('versioning/semver-coerced/index', () => { it('should return min satisfying version in range', () => { expect( semverCoerced.minSatisfyingVersion(['1.0.0', '1.0.4'], '^1.0') - ).toEqual('1.0.0'); + ).toBe('1.0.0'); }); it('should support coercion', () => { expect( semverCoerced.minSatisfyingVersion(['v1.0', '1.0.4-foo'], '^1.0') - ).toEqual('1.0.0'); + ).toBe('1.0.0'); }); }); @@ -207,13 +207,13 @@ describe('versioning/semver-coerced/index', () => { currentVersion: '1.0.0', newVersion: '1.1.0', }) - ).toEqual('1.1.0'); + ).toBe('1.1.0'); }); }); describe('.sortVersions(a, b)', () => { it('should return zero for equal versions', () => { - expect(semverCoerced.sortVersions('1.0.0', '1.0.0')).toEqual(0); + expect(semverCoerced.sortVersions('1.0.0', '1.0.0')).toBe(0); }); it('should return -1 for a < b', () => { @@ -221,11 +221,11 @@ describe('versioning/semver-coerced/index', () => { }); it('should return 1 for a > b', () => { - expect(semverCoerced.sortVersions('1.0.1', '1.0.0')).toEqual(1); + expect(semverCoerced.sortVersions('1.0.1', '1.0.0')).toBe(1); }); it('should return zero for equal non-strict versions', () => { - expect(semverCoerced.sortVersions('v1.0', '1.x')).toEqual(0); + expect(semverCoerced.sortVersions('v1.0', '1.x')).toBe(0); }); }); }); diff --git a/lib/versioning/swift/index.spec.ts b/lib/versioning/swift/index.spec.ts index 9bc5a45d99308b..dd1b8c812ce4a6 100644 --- a/lib/versioning/swift/index.spec.ts +++ b/lib/versioning/swift/index.spec.ts @@ -23,16 +23,19 @@ describe('versioning/swift/index', () => { version | expected ${'from: "1.2.3"'} | ${true} ${'from : "1.2.3"'} | ${true} + ${'from : "1.2.3.4.5"'} | ${false} ${'from:"1.2.3"'} | ${true} ${' from:"1.2.3" '} | ${true} ${' from : "1.2.3" '} | ${true} ${'"1.2.3"..."1.2.4"'} | ${true} ${' "1.2.3" ... "1.2.4" '} | ${true} ${'"1.2.3"...'} | ${true} + ${'"1.2.3.4.5"...'} | ${false} ${' "1.2.3" ... '} | ${true} ${'..."1.2.4"'} | ${true} ${' ... "1.2.4" '} | ${true} ${'"1.2.3"..<"1.2.4"'} | ${true} + ${'"1.2.3.4.5"..<"1.2.4"'} | ${false} ${' "1.2.3" ..< "1.2.4" '} | ${true} ${'..<"1.2.4"'} | ${true} ${' ..< "1.2.4" '} | ${true} @@ -86,7 +89,7 @@ describe('versioning/swift/index', () => { `( 'isLessThanRange("$version", "$range") === "$expected"', ({ version, range, expected }) => { - expect(isLessThanRange(version, range)).toBe(expected); + expect(isLessThanRange?.(version, range)).toBe(expected); } ); diff --git a/lib/versioning/swift/index.ts b/lib/versioning/swift/index.ts index fa7ba22a6bee6b..d3de422d7797fa 100644 --- a/lib/versioning/swift/index.ts +++ b/lib/versioning/swift/index.ts @@ -29,21 +29,36 @@ const { export const isValid = (input: string): boolean => !!valid(input) || !!validRange(toSemverRange(input)); + export const isVersion = (input: string): boolean => !!valid(input); -const getSatisfyingVersion = (versions: string[], range: string): string => - maxSatisfying( - versions.map((v) => v.replace(regEx(/^v/), '')), // TODO #12071 - toSemverRange(range) - ); -const minSatisfyingVersion = (versions: string[], range: string): string => - minSatisfying( - versions.map((v) => v.replace(regEx(/^v/), '')), // TODO #12071 #12070 - toSemverRange(range) - ); -const isLessThanRange = (version: string, range: string): boolean => - ltr(version, toSemverRange(range)); -const matches = (version: string, range: string): boolean => - satisfies(version, toSemverRange(range)); + +function getSatisfyingVersion( + versions: string[], + range: string +): string | null { + const normalizedVersions = versions.map((v) => v.replace(regEx(/^v/), '')); + const semverRange = toSemverRange(range); + return semverRange ? maxSatisfying(normalizedVersions, semverRange) : null; +} + +function minSatisfyingVersion( + versions: string[], + range: string +): string | null { + const normalizedVersions = versions.map((v) => v.replace(regEx(/^v/), '')); + const semverRange = toSemverRange(range); + return semverRange ? minSatisfying(normalizedVersions, semverRange) : null; +} + +function isLessThanRange(version: string, range: string): boolean { + const semverRange = toSemverRange(range); + return semverRange ? ltr(version, semverRange) : false; +} + +function matches(version: string, range: string): boolean { + const semverRange = toSemverRange(range); + return semverRange ? satisfies(version, semverRange) : false; +} export const api: VersioningApi = { equals, diff --git a/lib/versioning/swift/range.ts b/lib/versioning/swift/range.ts index 18ce3d20e0638b..955db8b68ca59d 100644 --- a/lib/versioning/swift/range.ts +++ b/lib/versioning/swift/range.ts @@ -7,27 +7,40 @@ const fromRange = regEx(/^\s*"([^"]+)"\s*\.\.\.\s*$/); const binaryRange = regEx(/^\s*"([^"]+)"\s*(\.\.[.<])\s*"([^"]+)"\s*$/); const toRange = regEx(/^\s*(\.\.[.<])\s*"([^"]+)"\s*$/); -function toSemverRange(range: string): string { - if (fromParam.test(range)) { - const [, version] = fromParam.exec(range); +function toSemverRange(range: string): string | null { + const fromParamMatch = fromParam.exec(range); + if (fromParamMatch) { + const [, version] = fromParamMatch; if (semver.valid(version)) { const nextMajor = `${semver.major(version) + 1}.0.0`; return `>=${version} <${nextMajor}`; } - } else if (fromRange.test(range)) { - const [, version] = fromRange.exec(range); + return null; + } + + const fromRangeMatch = fromRange.exec(range); + if (fromRangeMatch) { + const [, version] = fromRangeMatch; if (semver.valid(version)) { return `>=${version}`; } - } else if (binaryRange.test(range)) { - const [, currentVersion, op, newVersion] = binaryRange.exec(range); + return null; + } + + const binaryRangeMatch = binaryRange.exec(range); + if (binaryRangeMatch) { + const [, currentVersion, op, newVersion] = binaryRangeMatch; if (semver.valid(currentVersion) && semver.valid(newVersion)) { return op === '..<' ? `>=${currentVersion} <${newVersion}` : `>=${currentVersion} <=${newVersion}`; } - } else if (toRange.test(range)) { - const [, op, newVersion] = toRange.exec(range); + return null; + } + + const toRangeMatch = toRange.exec(range); + if (toRangeMatch) { + const [, op, newVersion] = toRangeMatch; if (semver.valid(newVersion)) { return op === '..<' ? `<${newVersion}` : `<=${newVersion}`; } @@ -35,26 +48,29 @@ function toSemverRange(range: string): string { return null; } -function getNewValue({ - currentValue, - currentVersion, - newVersion, -}: NewValueConfig): string { +function getNewValue({ currentValue, newVersion }: NewValueConfig): string { if (fromParam.test(currentValue)) { return currentValue.replace(regEx(/".*?"/), `"${newVersion}"`); } - if (fromRange.test(currentValue)) { - const [, version] = fromRange.exec(currentValue); + + const fromRangeMatch = fromRange.exec(currentValue); + if (fromRangeMatch) { + const [, version] = fromRangeMatch; return currentValue.replace(version, newVersion); } - if (binaryRange.test(currentValue)) { - const [, , , version] = binaryRange.exec(currentValue); + + const binaryRangeMatch = binaryRange.exec(currentValue); + if (binaryRangeMatch) { + const [, , , version] = binaryRangeMatch; return currentValue.replace(version, newVersion); } - if (toRange.test(currentValue)) { - const [, , version] = toRange.exec(currentValue); + + const toRangeMatch = toRange.exec(currentValue); + if (toRangeMatch) { + const [, , version] = toRangeMatch; return currentValue.replace(version, newVersion); } + return currentValue; } diff --git a/lib/versioning/ubuntu/index.spec.ts b/lib/versioning/ubuntu/index.spec.ts index 4f7362b4679767..99812ccdfb7156 100644 --- a/lib/versioning/ubuntu/index.spec.ts +++ b/lib/versioning/ubuntu/index.spec.ts @@ -121,6 +121,7 @@ describe('versioning/ubuntu/index', () => { ${'42.10'} | ${false} ${'42.11'} | ${false} ${'2020.04'} | ${false} + ${'22.04'} | ${false} `('isStable("$version") === $expected', ({ version, expected }) => { const res = !!ubuntu.isStable(version); expect(res).toBe(expected); diff --git a/lib/versioning/ubuntu/index.ts b/lib/versioning/ubuntu/index.ts index 0e4d3a22dd7a6c..07e470eedffd8f 100644 --- a/lib/versioning/ubuntu/index.ts +++ b/lib/versioning/ubuntu/index.ts @@ -6,6 +6,9 @@ export const displayName = 'Ubuntu'; export const urls = ['https://changelogs.ubuntu.com/meta-release']; export const supportsRanges = false; +// #12509 +const temporarilyUnstable = ['22.04']; + // validation function isValid(input: string): string | boolean | null { @@ -34,6 +37,9 @@ function isStable(version: string): boolean { if (!isValid(version)) { return false; } + if (temporarilyUnstable.includes(version)) { + return false; + } return regEx(/^\d?[02468]\.04/).test(version); } @@ -66,12 +72,12 @@ function getPatch(version: string): null | number { // comparison function equals(version: string, other: string): boolean { - return isVersion(version) && isVersion(other) && version === other; + return !!isVersion(version) && !!isVersion(other) && version === other; } function isGreaterThan(version: string, other: string): boolean { - const xMajor = getMajor(version); - const yMajor = getMajor(other); + const xMajor = getMajor(version) ?? 0; + const yMajor = getMajor(other) ?? 0; if (xMajor > yMajor) { return true; } @@ -79,8 +85,8 @@ function isGreaterThan(version: string, other: string): boolean { return false; } - const xMinor = getMinor(version); - const yMinor = getMinor(other); + const xMinor = getMinor(version) ?? 0; + const yMinor = getMinor(other) ?? 0; if (xMinor > yMinor) { return true; } @@ -88,8 +94,8 @@ function isGreaterThan(version: string, other: string): boolean { return false; } - const xPatch = getPatch(version) || 0; - const yPatch = getPatch(other) || 0; + const xPatch = getPatch(version) ?? 0; + const yPatch = getPatch(other) ?? 0; return xPatch > yPatch; } diff --git a/lib/workers/branch/artifacts.spec.ts b/lib/workers/branch/artifacts.spec.ts index d185d13c0611d0..d234cd7867ebe9 100644 --- a/lib/workers/branch/artifacts.spec.ts +++ b/lib/workers/branch/artifacts.spec.ts @@ -1,5 +1,5 @@ import { getConfig, platform } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { BranchStatus } from '../../types'; import { BranchConfig } from '../types'; import { setArtifactErrorStatus } from './artifacts'; @@ -7,7 +7,7 @@ import { setArtifactErrorStatus } from './artifacts'; describe('workers/branch/artifacts', () => { let config: BranchConfig; beforeEach(() => { - setGlobalConfig({}); + GlobalConfig.set({}); jest.resetAllMocks(); config = { ...getConfig(), @@ -31,7 +31,7 @@ describe('workers/branch/artifacts', () => { }); it('skips status (dry-run)', async () => { - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); platform.getBranchStatusCheck.mockResolvedValueOnce(null); await setArtifactErrorStatus(config); expect(platform.setBranchStatus).not.toHaveBeenCalled(); diff --git a/lib/workers/branch/artifacts.ts b/lib/workers/branch/artifacts.ts index 6c982bb99cee5a..8a82b19184003f 100644 --- a/lib/workers/branch/artifacts.ts +++ b/lib/workers/branch/artifacts.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import { platform } from '../../platform'; import { BranchStatus } from '../../types'; @@ -23,7 +23,7 @@ export async function setArtifactErrorStatus( // Check if state needs setting if (existingState !== state) { logger.debug(`Updating status check state to failed`); - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would set branch status in ' + config.branchName); } else { await platform.setBranchStatus({ diff --git a/lib/workers/branch/auto-replace.spec.ts b/lib/workers/branch/auto-replace.spec.ts index 99faf7b5ab30b2..e89415f4dd6dfc 100644 --- a/lib/workers/branch/auto-replace.spec.ts +++ b/lib/workers/branch/auto-replace.spec.ts @@ -103,7 +103,7 @@ describe('workers/branch/auto-replace', () => { reuseExistingBranch ); // FIXME: explicit assert condition - expect(res).toEqual('wrong source'); + expect(res).toBe('wrong source'); }); it('updates version and integrity', async () => { const script = diff --git a/lib/workers/branch/automerge.spec.ts b/lib/workers/branch/automerge.spec.ts index 9b6439f4fe3818..65de824a3ee5e4 100644 --- a/lib/workers/branch/automerge.spec.ts +++ b/lib/workers/branch/automerge.spec.ts @@ -1,5 +1,5 @@ import { defaultConfig, git, platform } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { BranchStatus } from '../../types'; import { tryBranchAutomerge } from './automerge'; @@ -13,7 +13,7 @@ describe('workers/branch/automerge', () => { config = { ...defaultConfig, }; - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns false if not configured for automerge', async () => { config.automerge = false; @@ -63,7 +63,7 @@ describe('workers/branch/automerge', () => { it('returns true if automerge succeeds (dry-run)', async () => { config.automerge = true; config.automergeType = 'branch'; - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); expect(await tryBranchAutomerge(config)).toBe('automerged'); }); diff --git a/lib/workers/branch/automerge.ts b/lib/workers/branch/automerge.ts index 8ae312e8ffd7b4..c2d53a1fc514ce 100644 --- a/lib/workers/branch/automerge.ts +++ b/lib/workers/branch/automerge.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { logger } from '../../logger'; import { platform } from '../../platform'; @@ -33,7 +33,7 @@ export async function tryBranchAutomerge( if (branchStatus === BranchStatus.green) { logger.debug(`Automerging branch`); try { - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would automerge branch' + config.branchName); } else { await mergeBranch(config.branchName); @@ -47,9 +47,13 @@ export async function tryBranchAutomerge( } if ( err.message.includes('refusing to merge unrelated histories') || - err.message.includes('Not possible to fast-forward') + err.message.includes('Not possible to fast-forward') || + err.message.includes( + 'Updates were rejected because the tip of your current branch is behind' + ) ) { - logger.warn({ err }, 'Branch is not up to date - cannot automerge'); + logger.debug({ err }, 'Branch automerge error'); + logger.info('Branch is not up to date - cannot automerge'); return 'stale'; } if (err.message.includes('Protected branch')) { diff --git a/lib/workers/branch/commit.spec.ts b/lib/workers/branch/commit.spec.ts index 6a99afd89cf361..c501a90c29a3a9 100644 --- a/lib/workers/branch/commit.spec.ts +++ b/lib/workers/branch/commit.spec.ts @@ -1,5 +1,5 @@ import { defaultConfig, git, partial } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { BranchConfig } from '../types'; import { commitFilesToBranch } from './commit'; @@ -21,7 +21,7 @@ describe('workers/branch/commit', () => { }); jest.resetAllMocks(); git.commitFiles.mockResolvedValueOnce('123test'); - setGlobalConfig(); + GlobalConfig.reset(); }); it('handles empty files', async () => { await commitFilesToBranch(config); @@ -37,7 +37,7 @@ describe('workers/branch/commit', () => { expect(git.commitFiles.mock.calls).toMatchSnapshot(); }); it('dry runs', async () => { - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); config.updatedPackageFiles.push({ name: 'package.json', contents: 'some contents', diff --git a/lib/workers/branch/commit.ts b/lib/workers/branch/commit.ts index 4a20b3c9b9cc56..bd201e7ea8ccdd 100644 --- a/lib/workers/branch/commit.ts +++ b/lib/workers/branch/commit.ts @@ -1,6 +1,6 @@ import is from '@sindresorhus/is'; import minimatch from 'minimatch'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { CONFIG_SECRETS_EXPOSED } from '../../constants/error-messages'; import { logger } from '../../logger'; import { commitFiles } from '../../util/git'; @@ -32,7 +32,7 @@ export function commitFilesToBranch( const fileLength = [...new Set(updatedFiles.map((file) => file.name))].length; logger.debug(`${fileLength} file(s) to commit`); // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would commit files to branch ' + config.branchName); return null; } @@ -41,6 +41,10 @@ export function commitFilesToBranch( config.branchName !== sanitize(config.branchName) || config.commitMessage !== sanitize(config.commitMessage) ) { + logger.debug( + { branchName: config.branchName }, + 'Secrets exposed in branchName or commitMessage' + ); throw new Error(CONFIG_SECRETS_EXPOSED); } // API will know whether to create new branch or not diff --git a/lib/workers/branch/execute-post-upgrade-commands.ts b/lib/workers/branch/execute-post-upgrade-commands.ts index 94cb192fa63a60..8a2fc6d0545e7f 100644 --- a/lib/workers/branch/execute-post-upgrade-commands.ts +++ b/lib/workers/branch/execute-post-upgrade-commands.ts @@ -1,11 +1,12 @@ import is from '@sindresorhus/is'; import minimatch from 'minimatch'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { addMeta, logger } from '../../logger'; import type { ArtifactError } from '../../manager/types'; import { exec } from '../../util/exec'; import { readLocalFile, writeLocalFile } from '../../util/fs'; -import { File, getRepoStatus } from '../../util/git'; +import { getRepoStatus } from '../../util/git'; +import type { File } from '../../util/git/types'; import { regEx } from '../../util/regex'; import { sanitize } from '../../util/sanitize'; import { compile } from '../../util/template'; @@ -23,7 +24,7 @@ export async function postUpgradeCommandsExecutor( let updatedArtifacts = [...(config.updatedArtifacts || [])]; const artifactErrors = [...(config.artifactErrors || [])]; const { allowedPostUpgradeCommands, allowPostUpgradeCommandTemplating } = - getGlobalConfig(); + GlobalConfig.get(); for (const upgrade of filteredUpgradeCommands) { addMeta({ dep: upgrade.depName }); @@ -62,7 +63,7 @@ export async function postUpgradeCommandsExecutor( logger.debug({ cmd: compiledCmd }, 'Executing post-upgrade task'); const execResult = await exec(compiledCmd, { - cwd: getGlobalConfig().localDir, + cwd: GlobalConfig.get('localDir'), }); logger.debug( @@ -149,7 +150,7 @@ export async function postUpgradeCommandsExecutor( export default async function executePostUpgradeCommands( config: BranchConfig ): Promise { - const { allowedPostUpgradeCommands } = getGlobalConfig(); + const { allowedPostUpgradeCommands } = GlobalConfig.get(); const hasChangedFiles = config.updatedPackageFiles?.length > 0 || diff --git a/lib/workers/branch/get-updated.ts b/lib/workers/branch/get-updated.ts index 49263f05d830fa..2bd880163a4e04 100644 --- a/lib/workers/branch/get-updated.ts +++ b/lib/workers/branch/get-updated.ts @@ -3,7 +3,8 @@ import { WORKER_FILE_UPDATE_FAILED } from '../../constants/error-messages'; import { logger } from '../../logger'; import { get } from '../../manager'; import type { ArtifactError, PackageDependency } from '../../manager/types'; -import { File, getFile } from '../../util/git'; +import { getFile } from '../../util/git'; +import { File } from '../../util/git/types'; import type { BranchConfig } from '../types'; import { doAutoReplace } from './auto-replace'; @@ -120,7 +121,7 @@ export async function getUpdatedPackageFiles( logger.debug({ packageFile, depName }, 'Contents updated'); updatedFileContents[packageFile] = res; } - continue; // eslint-disable-line no-continue + continue; } else if (reuseExistingBranch) { return getUpdatedPackageFiles({ ...config, diff --git a/lib/workers/branch/handle-existing.ts b/lib/workers/branch/handle-existing.ts index 969049e641ca80..e0d00108fe97bb 100644 --- a/lib/workers/branch/handle-existing.ts +++ b/lib/workers/branch/handle-existing.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import { Pr, platform } from '../../platform'; import { PrState } from '../../types'; @@ -19,7 +19,7 @@ export async function handlepr(config: BranchConfig, pr: Pr): Promise { '\n\nIf this PR was closed by mistake or you changed your mind, you can simply rename this PR and you will soon get a fresh replacement PR opened.'; if (!config.suppressNotifications.includes('prIgnoreNotification')) { const ignoreTopic = `Renovate Ignore Notification`; - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( `DRY-RUN: Would ensure closed PR comment in PR #${pr.number}` ); @@ -32,7 +32,7 @@ export async function handlepr(config: BranchConfig, pr: Pr): Promise { } } if (branchExists(config.branchName)) { - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would delete branch ' + config.branchName); } else { await deleteBranch(config.branchName); diff --git a/lib/workers/branch/index.spec.ts b/lib/workers/branch/index.spec.ts index 877672413d37c5..196ac118a3621f 100644 --- a/lib/workers/branch/index.spec.ts +++ b/lib/workers/branch/index.spec.ts @@ -1,6 +1,6 @@ import * as _fs from 'fs-extra'; import { defaultConfig, git, mocked, platform } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RepoGlobalConfig } from '../../config/types'; import { MANAGER_LOCKFILE_ERROR, @@ -10,7 +10,7 @@ import * as _npmPostExtract from '../../manager/npm/post-update'; import type { WriteExistingFilesResult } from '../../manager/npm/post-update/types'; import { PrState } from '../../types'; import * as _exec from '../../util/exec'; -import { File, StatusResult } from '../../util/git'; +import type { File, StatusResult } from '../../util/git/types'; import * as _mergeConfidence from '../../util/merge-confidence'; import * as _sanitize from '../../util/sanitize'; import * as _limits from '../global/limits'; @@ -93,7 +93,7 @@ describe('workers/branch/index', () => { body: '', }, }); - setGlobalConfig(adminConfig); + GlobalConfig.set(adminConfig); sanitize.sanitize.mockImplementation((input) => input); }); afterEach(() => { @@ -101,7 +101,7 @@ describe('workers/branch/index', () => { platform.ensureCommentRemoval.mockClear(); commit.commitFilesToBranch.mockClear(); jest.resetAllMocks(); - setGlobalConfig(); + GlobalConfig.reset(); }); it('skips branch if not scheduled and branch does not exist', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); @@ -427,7 +427,7 @@ describe('workers/branch/index', () => { git.branchExists.mockReturnValue(true); commit.commitFilesToBranch.mockResolvedValueOnce(null); automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged'); - setGlobalConfig({ ...adminConfig, dryRun: true }); + GlobalConfig.set({ ...adminConfig, dryRun: true }); await branchWorker.processBranch(config); expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(1); expect(prWorker.ensurePr).toHaveBeenCalledTimes(0); @@ -732,7 +732,7 @@ describe('workers/branch/index', () => { checkExisting.prAlreadyExisted.mockResolvedValueOnce({ state: PrState.Closed, } as Pr); - setGlobalConfig({ ...adminConfig, dryRun: true }); + GlobalConfig.set({ ...adminConfig, dryRun: true }); // FIXME: explicit assert condition expect(await branchWorker.processBranch(config)).toMatchSnapshot(); }); @@ -743,7 +743,7 @@ describe('workers/branch/index', () => { state: PrState.Open, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); - setGlobalConfig({ ...adminConfig, dryRun: true }); + GlobalConfig.set({ ...adminConfig, dryRun: true }); // FIXME: explicit assert condition expect(await branchWorker.processBranch(config)).toMatchSnapshot(); }); @@ -766,7 +766,7 @@ describe('workers/branch/index', () => { git.isBranchModified.mockResolvedValueOnce(true); schedule.isScheduledNow.mockReturnValueOnce(false); commit.commitFilesToBranch.mockResolvedValueOnce(null); - setGlobalConfig({ ...adminConfig, dryRun: true }); + GlobalConfig.set({ ...adminConfig, dryRun: true }); // FIXME: explicit assert condition expect( await branchWorker.processBranch({ @@ -799,7 +799,7 @@ describe('workers/branch/index', () => { pr: {}, } as EnsurePrResult); commit.commitFilesToBranch.mockResolvedValueOnce(null); - setGlobalConfig({ ...adminConfig, dryRun: true }); + GlobalConfig.set({ ...adminConfig, dryRun: true }); // FIXME: explicit assert condition expect( await branchWorker.processBranch({ @@ -875,7 +875,7 @@ describe('workers/branch/index', () => { schedule.isScheduledNow.mockReturnValueOnce(false); commit.commitFilesToBranch.mockResolvedValueOnce(null); - setGlobalConfig({ + GlobalConfig.set({ ...adminConfig, allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'], allowPostUpgradeCommandTemplating: true, @@ -953,7 +953,7 @@ describe('workers/branch/index', () => { schedule.isScheduledNow.mockReturnValueOnce(false); commit.commitFilesToBranch.mockResolvedValueOnce(null); - setGlobalConfig({ + GlobalConfig.set({ ...adminConfig, allowedPostUpgradeCommands: ['^exit 1$'], allowPostUpgradeCommandTemplating: true, @@ -1022,7 +1022,7 @@ describe('workers/branch/index', () => { schedule.isScheduledNow.mockReturnValueOnce(false); commit.commitFilesToBranch.mockResolvedValueOnce(null); - setGlobalConfig({ + GlobalConfig.set({ ...adminConfig, allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'], allowPostUpgradeCommandTemplating: false, @@ -1103,7 +1103,7 @@ describe('workers/branch/index', () => { schedule.isScheduledNow.mockReturnValueOnce(false); commit.commitFilesToBranch.mockResolvedValueOnce(null); - setGlobalConfig({ + GlobalConfig.set({ ...adminConfig, allowedPostUpgradeCommands: ['^echo {{{depName}}}$'], allowPostUpgradeCommandTemplating: true, @@ -1196,7 +1196,7 @@ describe('workers/branch/index', () => { (f) => f.contents === 'modified_then_deleted_file' && f.name === '|delete|' ) - ).not.toBeUndefined(); + ).toBeDefined(); }); it('executes post-upgrade tasks once when set to branch mode', async () => { @@ -1238,7 +1238,7 @@ describe('workers/branch/index', () => { schedule.isScheduledNow.mockReturnValueOnce(false); commit.commitFilesToBranch.mockResolvedValueOnce(null); - setGlobalConfig({ + GlobalConfig.set({ ...adminConfig, allowedPostUpgradeCommands: ['^echo hardcoded-string$'], allowPostUpgradeCommandTemplating: true, diff --git a/lib/workers/branch/index.ts b/lib/workers/branch/index.ts index 00d27a9ac4fc83..4173209ffa0c0d 100644 --- a/lib/workers/branch/index.ts +++ b/lib/workers/branch/index.ts @@ -1,5 +1,5 @@ import { DateTime } from 'luxon'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { CONFIG_VALIDATION, @@ -289,7 +289,7 @@ export async function processBranch( 'Update has not passed stability days' ); config.stabilityStatus = BranchStatus.yellow; - continue; // eslint-disable-line no-continue + continue; } } const { @@ -316,7 +316,7 @@ export async function processBranch( 'Update does not meet minimum confidence scores' ); config.confidenceStatus = BranchStatus.yellow; - continue; // eslint-disable-line no-continue + continue; } } } @@ -418,7 +418,7 @@ export async function processBranch( } else if (config.updatedArtifacts?.length && branchPr) { // If there are artifacts, no errors, and an existing PR then ensure any artifacts error comment is removed // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( `DRY-RUN: Would ensure comment removal in PR #${branchPr.number}` ); @@ -484,7 +484,7 @@ export async function processBranch( const mergeStatus = await tryBranchAutomerge(config); logger.debug(`mergeStatus=${mergeStatus}`); if (mergeStatus === 'automerged') { - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would delete branch' + config.branchName); } else { await deleteBranchSilently(config.branchName); @@ -647,7 +647,7 @@ export async function processBranch( ' - any of the package files in this branch needs updating, or \n'; content += ' - the branch becomes conflicted, or\n'; content += - ' - you check the rebase/retry checkbox if found above, or\n'; + ' - you click the rebase/retry checkbox if found above, or\n'; content += ' - you rename this PR\'s title to start with "rebase!" to trigger it manually'; content += '\n\nThe artifact failure details are included below:\n\n'; @@ -662,7 +662,7 @@ export async function processBranch( config.suppressNotifications.includes('lockFileErrors') ) ) { - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( `DRY-RUN: Would ensure lock file error comment in PR #${pr.number}` ); diff --git a/lib/workers/branch/lock-files/index.spec.ts b/lib/workers/branch/lock-files/index.spec.ts index f226dcec5a8775..6adeb1205d2f0f 100644 --- a/lib/workers/branch/lock-files/index.spec.ts +++ b/lib/workers/branch/lock-files/index.spec.ts @@ -1,6 +1,6 @@ import { git, mocked } from '../../../../test/util'; import { getConfig } from '../../../config/defaults'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import * as _lockFiles from '../../../manager/npm/post-update'; import * as _lerna from '../../../manager/npm/post-update/lerna'; import * as _npm from '../../../manager/npm/post-update/npm'; @@ -31,7 +31,7 @@ const { writeUpdatedPackageFiles, getAdditionalFiles } = lockFiles; describe('workers/branch/lock-files/index', () => { describe('writeUpdatedPackageFiles', () => { beforeEach(() => { - setGlobalConfig({ + GlobalConfig.set({ localDir: 'some-tmp-dir', }); fs.outputFile = jest.fn(); @@ -70,7 +70,7 @@ describe('workers/branch/lock-files/index', () => { }); describe('getAdditionalFiles', () => { beforeEach(() => { - setGlobalConfig({ + GlobalConfig.set({ localDir: 'some-tmp-dir', }); git.getFile.mockResolvedValueOnce('some lock file contents'); diff --git a/lib/workers/branch/reuse.ts b/lib/workers/branch/reuse.ts index 8d53647b3e180a..a114fd864fa7d7 100644 --- a/lib/workers/branch/reuse.ts +++ b/lib/workers/branch/reuse.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import { platform } from '../../platform'; import type { RangeStrategy } from '../../types'; @@ -36,7 +36,7 @@ export async function shouldReuseExistingBranch( if (pr.labels?.includes(config.rebaseLabel)) { logger.debug(`Manual rebase requested via PR labels for #${pr.number}`); // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( `DRY-RUN: Would delete label ${config.rebaseLabel} from #${pr.number}` ); diff --git a/lib/workers/global/config/parse/cli.spec.ts b/lib/workers/global/config/parse/cli.spec.ts index 76455df6f260f7..cc9aa78092b25e 100644 --- a/lib/workers/global/config/parse/cli.spec.ts +++ b/lib/workers/global/config/parse/cli.spec.ts @@ -13,14 +13,14 @@ describe('workers/global/config/parse/cli', () => { const option: Partial = { name: 'oneTwoThree', }; - expect(cli.getCliName(option)).toEqual('--one-two-three'); + expect(cli.getCliName(option)).toBe('--one-two-three'); }); it('generates returns empty if CLI false', () => { const option: Partial = { name: 'oneTwoThree', cli: false, }; - expect(cli.getCliName(option)).toEqual(''); + expect(cli.getCliName(option)).toBe(''); }); }); describe('.getConfig(argv)', () => { diff --git a/lib/workers/global/config/parse/cli.ts b/lib/workers/global/config/parse/cli.ts index a318e9944151c1..4bc1d6c977f292 100644 --- a/lib/workers/global/config/parse/cli.ts +++ b/lib/workers/global/config/parse/cli.ts @@ -85,9 +85,9 @@ export function getConfig(input: string[]): AllConfig { } }); + /* eslint-disable no-console */ /* istanbul ignore next */ function helpConsole(): void { - /* eslint-disable no-console */ console.log(' Examples:'); console.log(''); console.log(' $ renovate --token 123test singapore/lint-condo'); diff --git a/lib/workers/global/config/parse/env.spec.ts b/lib/workers/global/config/parse/env.spec.ts index 432183313c4dcc..725685ad2f002b 100644 --- a/lib/workers/global/config/parse/env.spec.ts +++ b/lib/workers/global/config/parse/env.spec.ts @@ -202,6 +202,7 @@ describe('workers/global/config/parse/env', () => { beforeAll(() => { processExit = jest .spyOn(process, 'exit') + // eslint-disable-next-line @typescript-eslint/no-empty-function .mockImplementation((() => {}) as never); }); @@ -231,20 +232,20 @@ describe('workers/global/config/parse/env', () => { name: 'foo', env: false, }; - expect(env.getEnvName(option)).toEqual(''); + expect(env.getEnvName(option)).toBe(''); }); it('returns existing env', () => { const option: Partial = { name: 'foo', env: 'FOO', }; - expect(env.getEnvName(option)).toEqual('FOO'); + expect(env.getEnvName(option)).toBe('FOO'); }); it('generates RENOVATE_ env', () => { const option: Partial = { name: 'oneTwoThree', }; - expect(env.getEnvName(option)).toEqual('RENOVATE_ONE_TWO_THREE'); + expect(env.getEnvName(option)).toBe('RENOVATE_ONE_TWO_THREE'); }); }); }); diff --git a/lib/workers/global/config/parse/env.ts b/lib/workers/global/config/parse/env.ts index f7268d74cb3cdc..ca969854314faf 100644 --- a/lib/workers/global/config/parse/env.ts +++ b/lib/workers/global/config/parse/env.ts @@ -129,7 +129,7 @@ export function getConfig(inputEnv: NodeJS.ProcessEnv): AllConfig { 'VSTS_ENDPOINT', 'VSTS_TOKEN', ]; - // eslint-disable-next-line no-param-reassign + unsupportedEnv.forEach((val) => delete env[val]); return config; diff --git a/lib/workers/global/config/parse/file.spec.ts b/lib/workers/global/config/parse/file.spec.ts index 0934b9d0be1ff7..6e629aa2448248 100644 --- a/lib/workers/global/config/parse/file.spec.ts +++ b/lib/workers/global/config/parse/file.spec.ts @@ -16,24 +16,24 @@ describe('workers/global/config/parse/file', () => { }); describe('.getConfig()', () => { - it('parses custom config file', () => { + it('parses custom config file', async () => { const configFile = upath.resolve(__dirname, './__fixtures__/file.js'); - expect(file.getConfig({ RENOVATE_CONFIG_FILE: configFile })).toEqual( - customConfig - ); + expect( + await file.getConfig({ RENOVATE_CONFIG_FILE: configFile }) + ).toEqual(customConfig); }); - it('migrates', () => { + it('migrates', async () => { const configFile = upath.resolve(__dirname, './__fixtures__/file2.js'); - const res = file.getConfig({ RENOVATE_CONFIG_FILE: configFile }); + const res = await file.getConfig({ RENOVATE_CONFIG_FILE: configFile }); expect(res).toMatchSnapshot(); - expect(res.rangeStrategy).toEqual('bump'); + expect(res.rangeStrategy).toBe('bump'); }); - it('parse and returns empty config if there is no RENOVATE_CONFIG_FILE in env', () => { - expect(file.getConfig({})).toBeDefined(); + it('parse and returns empty config if there is no RENOVATE_CONFIG_FILE in env', async () => { + expect(await file.getConfig({})).toBeDefined(); }); - it('fatal error and exit if error in parsing config.js', () => { + it('fatal error and exit if error in parsing config.js', async () => { const mockProcessExit = jest .spyOn(process, 'exit') .mockImplementation(() => undefined as never); @@ -50,19 +50,19 @@ describe('workers/global/config/parse/file', () => { "repositories": [ "test/test" ], };`; fs.writeFileSync(configFile, fileContent, { encoding: 'utf8' }); - file.getConfig({ RENOVATE_CONFIG_FILE: configFile }); + await file.getConfig({ RENOVATE_CONFIG_FILE: configFile }); expect(mockProcessExit).toHaveBeenCalledWith(1); fs.unlinkSync(configFile); }); - it('fatal error and exit if custom config file does not exist', () => { + it('fatal error and exit if custom config file does not exist', async () => { const mockProcessExit = jest .spyOn(process, 'exit') .mockImplementation(() => undefined as never); const configFile = upath.resolve(tmp.path, './file4.js'); - file.getConfig({ RENOVATE_CONFIG_FILE: configFile }); + await file.getConfig({ RENOVATE_CONFIG_FILE: configFile }); expect(mockProcessExit).toHaveBeenCalledWith(1); }); diff --git a/lib/workers/global/config/parse/file.ts b/lib/workers/global/config/parse/file.ts index 0acf7b0aaebb50..efdefb62b313c0 100644 --- a/lib/workers/global/config/parse/file.ts +++ b/lib/workers/global/config/parse/file.ts @@ -3,7 +3,7 @@ import { migrateConfig } from '../../../../config/migration'; import type { AllConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; -export function getConfig(env: NodeJS.ProcessEnv): AllConfig { +export async function getConfig(env: NodeJS.ProcessEnv): Promise { let configFile = env.RENOVATE_CONFIG_FILE || 'config'; if (!upath.isAbsolute(configFile)) { configFile = `${process.cwd()}/${configFile}`; @@ -11,8 +11,8 @@ export function getConfig(env: NodeJS.ProcessEnv): AllConfig { } let config: AllConfig = {}; try { - // eslint-disable-next-line global-require,import/no-dynamic-require - config = require(configFile); + const tmpConfig = await import(configFile); + config = tmpConfig.default ? tmpConfig.default : tmpConfig; } catch (err) { // istanbul ignore if if (err instanceof SyntaxError || err instanceof TypeError) { diff --git a/lib/workers/global/config/parse/host-rules-from-env.ts b/lib/workers/global/config/parse/host-rules-from-env.ts index 5f6a8189f61395..a25475c4b435b2 100644 --- a/lib/workers/global/config/parse/host-rules-from-env.ts +++ b/lib/workers/global/config/parse/host-rules-from-env.ts @@ -13,7 +13,7 @@ export function hostRulesFromEnv(env: NodeJS.ProcessEnv): HostRule[] { for (const envName of Object.keys(env).sort()) { if (npmEnvPrefixes.some((prefix) => envName.startsWith(prefix))) { logger.trace('Ignoring npm env: ' + envName); - continue; // eslint-disable-line no-continue + continue; } // Double underscore __ is used in place of hyphen - const splitEnv = envName.toLowerCase().replace(/__/g, '-').split('_'); @@ -28,7 +28,7 @@ export function hostRulesFromEnv(env: NodeJS.ProcessEnv): HostRule[] { // host-less rule } else if (splitEnv.length === 1) { logger.warn(`Cannot parse ${envName} env`); - continue; // eslint-disable-line no-continue + continue; } else { matchHost = splitEnv.join('.'); } diff --git a/lib/workers/global/config/parse/index.spec.ts b/lib/workers/global/config/parse/index.spec.ts index 5fd74b6d3920e4..10503f757c2b16 100644 --- a/lib/workers/global/config/parse/index.spec.ts +++ b/lib/workers/global/config/parse/index.spec.ts @@ -121,7 +121,7 @@ describe('workers/global/config/parse/index', () => { '--endpoint=https://github.renovatebot.com/api/v3', ]); const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv); - expect(parsed.endpoint).toEqual('https://github.renovatebot.com/api/v3/'); + expect(parsed.endpoint).toBe('https://github.renovatebot.com/api/v3/'); }); it('parses global manager config', async () => { defaultArgv = defaultArgv.concat(['--detect-global-manager-config=true']); diff --git a/lib/workers/global/config/parse/index.ts b/lib/workers/global/config/parse/index.ts index 35306b784dbd08..a0ec060d149a31 100644 --- a/lib/workers/global/config/parse/index.ts +++ b/lib/workers/global/config/parse/index.ts @@ -18,7 +18,7 @@ export async function parseConfigs( // Get configs const defaultConfig = defaultsParser.getConfig(); - const fileConfig = fileParser.getConfig(env); + const fileConfig = await fileParser.getConfig(env); const cliConfig = cliParser.getConfig(argv); const envConfig = envParser.getConfig(env); diff --git a/lib/workers/global/index.spec.ts b/lib/workers/global/index.spec.ts index 92f46e7e91ca4f..237bcd76690309 100644 --- a/lib/workers/global/index.spec.ts +++ b/lib/workers/global/index.spec.ts @@ -31,7 +31,7 @@ describe('workers/global/index', () => { maintainYarnLock: true, foo: 1, }); - await expect(globalWorker.start()).resolves.toEqual(0); + await expect(globalWorker.start()).resolves.toBe(0); }); it('handles zero repos', async () => { configParser.parseConfigs.mockResolvedValueOnce({ @@ -39,7 +39,7 @@ describe('workers/global/index', () => { cacheDir: '/tmp/cache', repositories: [], }); - await expect(globalWorker.start()).resolves.toEqual(0); + await expect(globalWorker.start()).resolves.toBe(0); }); it('processes repositories', async () => { configParser.parseConfigs.mockResolvedValueOnce({ @@ -90,7 +90,7 @@ describe('workers/global/index', () => { msg: 'meh', }, ]); - await expect(globalWorker.start()).resolves.not.toEqual(0); + await expect(globalWorker.start()).resolves.not.toBe(0); }); it('exits with zero when warnings are logged', async () => { configParser.parseConfigs.mockResolvedValueOnce({ @@ -105,7 +105,7 @@ describe('workers/global/index', () => { msg: 'meh', }, ]); - await expect(globalWorker.start()).resolves.toEqual(0); + await expect(globalWorker.start()).resolves.toBe(0); }); describe('processes platforms', () => { it('github', async () => { @@ -140,7 +140,7 @@ describe('workers/global/index', () => { }); fs.writeFile.mockReturnValueOnce(null); - expect(await globalWorker.start()).toEqual(0); + expect(await globalWorker.start()).toBe(0); expect(fs.writeFile).toHaveBeenCalledTimes(1); expect(fs.writeFile).toHaveBeenCalledWith( '/tmp/renovate-output.json', diff --git a/lib/workers/global/initialize.spec.ts b/lib/workers/global/initialize.spec.ts new file mode 100644 index 00000000000000..27df1dca77973a --- /dev/null +++ b/lib/workers/global/initialize.spec.ts @@ -0,0 +1,21 @@ +import { git } from '../../../test/util'; +import type { RenovateConfig } from '../../config/types'; +import { globalInitialize } from './initialize'; + +jest.mock('../../util/git'); + +describe('workers/global/initialize', () => { + describe('checkVersions()', () => { + it('throws if invalid version', async () => { + const config: RenovateConfig = {}; + git.validateGitVersion.mockResolvedValueOnce(false); + await expect(globalInitialize(config)).rejects.toThrow(); + }); + + it('returns if valid git version', async () => { + const config: RenovateConfig = {}; + git.validateGitVersion.mockResolvedValueOnce(true); + await expect(globalInitialize(config)).toResolve(); + }); + }); +}); diff --git a/lib/workers/global/initialize.ts b/lib/workers/global/initialize.ts index c82fc237bbd24c..fa374d1a5ac0bf 100644 --- a/lib/workers/global/initialize.ts +++ b/lib/workers/global/initialize.ts @@ -6,6 +6,7 @@ import { logger } from '../../logger'; import { initPlatform } from '../../platform'; import * as packageCache from '../../util/cache/package'; import { setEmojiConfig } from '../../util/emoji'; +import { validateGitVersion } from '../../util/git'; import { Limit, setMaxLimit } from './limits'; async function setDirectories(input: AllConfig): Promise { @@ -34,10 +35,18 @@ function limitCommitsPerRun(config: RenovateConfig): void { setMaxLimit(Limit.Commits, limit); } +async function checkVersions(): Promise { + const validGitVersion = await validateGitVersion(); + if (!validGitVersion) { + throw new Error('Init: git version needs upgrading'); + } +} + export async function globalInitialize( config_: RenovateConfig ): Promise { let config = config_; + await checkVersions(); config = await initPlatform(config); config = await setDirectories(config); packageCache.init(config); diff --git a/lib/workers/pr/__snapshots__/index.spec.ts.snap b/lib/workers/pr/__snapshots__/index.spec.ts.snap index 7ff26d4911972d..296ae0bf1cc9eb 100644 --- a/lib/workers/pr/__snapshots__/index.spec.ts.snap +++ b/lib/workers/pr/__snapshots__/index.spec.ts.snap @@ -137,7 +137,7 @@ Array [ "gitLabIgnoreApprovals": false, "usePlatformAutomerge": false, }, - "prBody": "This PR contains the following updates:\\n\\n| Package | Type | Update | Change |\\n|---|---|---|---|\\n| [dummy](https://dummy.com) ([source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md)) | devDependencies | lockFileMaintenance | \`1.0.0\` -> \`1.1.0\` |\\n| a | | | \`zzzzzz\` -> \`aaaaaaa\` |\\n| b | | pin | \`some_old_value\` -> \`some_new_value\` |\\n| c | | | \`\` -> \`\` |\\n| d | | lockFileMaintenance | \`\` -> \`\` |\\n\\nnote 1\\n\\nnote 2\\n\\n:warning: Release Notes retrieval for this PR were skipped because no github.com credentials were available.\\nIf you are self-hosted, please see [this instruction](https://github.com/renovatebot/renovate/blob/master/docs/usage/examples/self-hosting.md#githubcom-token-for-release-notes).\\n\\n🔡 If you wish to disable git hash updates, add \`\\":disableDigestUpdates\\"\` to the extends array in your config.\\n\\n🔧 This Pull Request updates lock files to use the latest dependency versions.\\n\\n---\\n\\n### Release Notes\\n\\n
\\nrenovateapp/dummy\\n\\n### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n[Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n
\\n\\n
\\nrenovateapp/dummy\\n\\n
\\n\\n---\\n\\n### Configuration\\n\\n📅 **Schedule**: At any time (no schedule defined).\\n\\nđŸšĻ **Automerge**: Disabled by config. Please merge this manually once you are satisfied.\\n\\nâ™ģ **Rebasing**: Never, or you tick the rebase/retry checkbox.\\n\\nđŸ‘ģ **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.\\n\\n---\\n\\n - [ ] If you want to rebase/retry this PR, click this checkbox.\\n\\n---\\n\\nThis PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).", + "prBody": "This PR contains the following updates:\\n\\n| Package | Type | Update | Change |\\n|---|---|---|---|\\n| [dummy](https://dummy.com) ([source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md)) | devDependencies | lockFileMaintenance | \`1.0.0\` -> \`1.1.0\` |\\n| a | | | \`zzzzzz\` -> \`aaaaaaa\` |\\n| b | | pin | \`some_old_value\` -> \`some_new_value\` |\\n| c | | | \`\` -> \`\` |\\n| d | | lockFileMaintenance | \`\` -> \`\` |\\n| e | | lockFileMaintenance | \`\` -> \`\` |\\n| f | | lockFileMaintenance | \`\` -> \`\` |\\n\\nnote 1\\n\\nnote 2\\n\\n:warning: Release Notes retrieval for this PR were skipped because no github.com credentials were available.\\nIf you are self-hosted, please see [this instruction](https://github.com/renovatebot/renovate/blob/master/docs/usage/examples/self-hosting.md#githubcom-token-for-release-notes).\\n\\n🔡 If you wish to disable git hash updates, add \`\\":disableDigestUpdates\\"\` to the extends array in your config.\\n\\n🔧 This Pull Request updates lock files to use the latest dependency versions.\\n\\n---\\n\\n### Release Notes\\n\\n
\\nrenovateapp/dummy (dummy)\\n\\n### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n[Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n
\\n\\n
\\nrenovateapp/dummy (b)\\n\\n### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n[Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n
\\n\\n
\\nrenovateapp/dummy (c)\\n\\n
\\n\\n
\\nrenovateapp/dummy (d)\\n\\n
\\n\\n
\\nrenovateapp/dummymonorepo\\n\\n### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n[Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)\\n\\n
\\n\\n---\\n\\n### Configuration\\n\\n📅 **Schedule**: At any time (no schedule defined).\\n\\nđŸšĻ **Automerge**: Disabled by config. Please merge this manually once you are satisfied.\\n\\nâ™ģ **Rebasing**: Never, or you tick the rebase/retry checkbox.\\n\\nđŸ‘ģ **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.\\n\\n---\\n\\n - [ ] If you want to rebase/retry this PR, click this checkbox.\\n\\n---\\n\\nThis PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).", "prTitle": "Update dependency dummy to v1.1.0", "sourceBranch": "renovate/dummy-1.x", "targetBranch": undefined, diff --git a/lib/workers/pr/automerge.ts b/lib/workers/pr/automerge.ts index f96dd04b3dd8ae..a03f15886da1c8 100644 --- a/lib/workers/pr/automerge.ts +++ b/lib/workers/pr/automerge.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; import { Pr, platform } from '../../platform'; import { BranchStatus } from '../../types'; @@ -76,7 +76,7 @@ export async function checkAutoMerge( if (automergeType === 'pr-comment') { logger.debug(`Applying automerge comment: ${automergeComment}`); // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( `DRY-RUN: Would add PR automerge comment to PR #${pr.number}` ); @@ -100,7 +100,7 @@ export async function checkAutoMerge( } // Let's merge this // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( `DRY-RUN: Would merge PR #${pr.number} with strategy "${automergeStrategy}"` ); diff --git a/lib/workers/pr/body/changelogs.ts b/lib/workers/pr/body/changelogs.ts index 73aa37c51d5f77..ad7b3660e5ebe8 100644 --- a/lib/workers/pr/body/changelogs.ts +++ b/lib/workers/pr/body/changelogs.ts @@ -11,6 +11,26 @@ export function getChangelogs(config: BranchConfig): string { if (!config.hasReleaseNotes) { return releaseNotes; } + + const countReleaseNodesByRepoName: Record = {}; + + for (const upgrade of config.upgrades) { + if (upgrade.hasReleaseNotes) { + countReleaseNodesByRepoName[upgrade.repoName] = + (countReleaseNodesByRepoName[upgrade.repoName] || 0) + 1; + } + } + + for (const upgrade of config.upgrades) { + if (upgrade.hasReleaseNotes) { + upgrade.releaseNotesSummaryTitle = `${upgrade.repoName}${ + countReleaseNodesByRepoName[upgrade.repoName] > 1 + ? ` (${upgrade.depName})` + : '' + }`; + } + } + releaseNotes += '\n\n---\n\n' + template.compile(releaseNotesHbs, config, false) + '\n\n'; releaseNotes = releaseNotes.replace(regEx(/### \[`vv/g), '### [`v'); diff --git a/lib/workers/pr/body/index.ts b/lib/workers/pr/body/index.ts index 6aa5a015af8b64..0a3f509fc92711 100644 --- a/lib/workers/pr/body/index.ts +++ b/lib/workers/pr/body/index.ts @@ -12,7 +12,6 @@ import { getPrUpdatesTable } from './updates-table'; function massageUpdateMetadata(config: BranchConfig): void { config.upgrades.forEach((upgrade) => { - /* eslint-disable no-param-reassign */ const { homepage, sourceUrl, @@ -54,7 +53,6 @@ function massageUpdateMetadata(config: BranchConfig): void { references.push(`[changelog](${changelogUrl})`); } upgrade.references = references.join(', '); - /* eslint-enable no-param-reassign */ }); } diff --git a/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap index a97678ef16b0b3..dfc462b844d482 100644 --- a/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap @@ -34,6 +34,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -45,6 +46,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -87,6 +89,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github-enterprise.example.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -98,6 +101,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github-enterprise.example.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -140,6 +144,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -151,6 +156,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -193,6 +199,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -204,6 +211,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -246,6 +254,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -257,6 +266,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -299,6 +309,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -310,6 +321,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", diff --git a/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap index d02b4cdfbaf35f..ae886d87da5b3e 100644 --- a/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap @@ -259,6 +259,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://gitlab.com/meno/dropzone/compare/v5.6.0...v5.6.1", }, "version": "5.6.1", @@ -270,6 +271,7 @@ Object { }, "date": "2020-02-13T15:37:00.000Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://gitlab.com/meno/dropzone/compare/v5.5.0...v5.6.0", }, "version": "5.6.0", @@ -281,6 +283,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://gitlab.com/meno/dropzone/compare/v5.4.0...v5.5.0", }, "version": "5.5.0", @@ -292,6 +295,7 @@ Object { }, "date": "2018-08-24T14:23:00.000Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://gitlab.com/meno/dropzone/compare/v5.2.0...v5.4.0", }, "version": "5.4.0", diff --git a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap index f57ff11e1bfdda..688c33142244d7 100644 --- a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap @@ -34,6 +34,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -45,6 +46,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -191,6 +193,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github-enterprise.example.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -202,6 +205,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github-enterprise.example.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -392,6 +396,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -403,6 +408,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -593,6 +599,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github-enterprise.example.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -604,6 +611,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github-enterprise.example.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -794,6 +802,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -805,6 +814,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", @@ -840,6 +850,7 @@ Object { }, "date": "2017-12-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/v2.3.0...v2.4.2", }, "version": "2.4.2", @@ -851,6 +862,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/2.2.2...v2.3.0", }, "version": "2.3.0", @@ -862,6 +874,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/1.0.0...2.2.2", }, "version": "2.2.2", @@ -1052,6 +1065,7 @@ Object { }, "date": "2017-10-24T03:20:46.238Z", "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_2.2.2...npm_2.3.0", }, "version": "2.3.0", @@ -1063,6 +1077,7 @@ Object { }, "date": undefined, "releaseNotes": Object { + "notesSourceUrl": "", "url": "https://github.com/chalk/chalk/compare/npm_1.0.0...npm_2.2.2", }, "version": "2.2.2", diff --git a/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap index 4386f098d4e6cd..bd95f1cb5cd9c4 100644 --- a/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap @@ -46,6 +46,7 @@ Array [ "body": undefined, "id": undefined, "name": undefined, + "notesSourceUrl": "https://api.github.com/repos/some/yet-other-repository/releases", "tag": "v1.0.0", "url": undefined, }, @@ -53,6 +54,7 @@ Array [ "body": "some body #123, [#124](https://github.com/some/yet-other-repository/issues/124)", "id": undefined, "name": undefined, + "notesSourceUrl": "https://api.github.com/repos/some/yet-other-repository/releases", "tag": "v1.0.1", "url": undefined, }, @@ -79,12 +81,14 @@ Array [ Object { "body": undefined, "name": undefined, + "notesSourceUrl": "https://gitlab.com/api/v4/projects/some%2fyet-other-repository/releases", "tag": "v1.0.0", "url": "https://gitlab.com/api/v4/projects/some%2fyet-other-repository/releases/v1.0.0", }, Object { "body": undefined, "name": undefined, + "notesSourceUrl": "https://gitlab.com/api/v4/projects/some%2fyet-other-repository/releases", "tag": "v1.0.1", "url": "https://gitlab.com/api/v4/projects/some%2fyet-other-repository/releases/v1.0.1", }, @@ -111,12 +115,14 @@ Array [ Object { "body": undefined, "name": undefined, + "notesSourceUrl": "https://my.custom.domain/api/v4/projects/some%2fyet-other-repository/releases", "tag": "v1.0.0", "url": "https://my.custom.domain/api/v4/projects/some%2fyet-other-repository/releases/v1.0.0", }, Object { "body": undefined, "name": undefined, + "notesSourceUrl": "https://my.custom.domain/api/v4/projects/some%2fyet-other-repository/releases", "tag": "v1.0.1", "url": "https://my.custom.domain/api/v4/projects/some%2fyet-other-repository/releases/v1.0.1", }, @@ -145,6 +151,7 @@ Object { ", "id": undefined, "name": undefined, + "notesSourceUrl": "https://api.github.com/repos/some/other-repository/releases", "tag": "1.0.1", "url": "https://github.com/some/other-repository/releases/1.0.1", } @@ -171,6 +178,7 @@ Object { ", "id": undefined, "name": undefined, + "notesSourceUrl": "https://api.github.com/repos/some/other-repository/releases", "tag": "other@1.0.1", "url": "https://github.com/some/other-repository/releases/other@1.0.1", } @@ -197,6 +205,7 @@ Object { ", "id": undefined, "name": undefined, + "notesSourceUrl": "https://api.github.com/repos/some/other-repository/releases", "tag": "other_v1.0.1", "url": "https://github.com/some/other-repository/releases/other_v1.0.1", } @@ -223,6 +232,7 @@ Object { ", "id": undefined, "name": undefined, + "notesSourceUrl": "https://api.github.com/repos/some/other-repository/releases", "tag": "other-1.0.1", "url": "https://github.com/some/other-repository/releases/other-1.0.1", } @@ -249,6 +259,7 @@ Object { ", "id": undefined, "name": undefined, + "notesSourceUrl": "https://api.github.com/repos/some/other-repository/releases", "tag": "v1.0.1", "url": "https://github.com/some/other-repository/releases/v1.0.1", } @@ -273,6 +284,7 @@ exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes Object { "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)", "name": undefined, + "notesSourceUrl": "https://api.gitlab.com/projects/some%2fother-repository/releases", "tag": "1.0.1", "url": "https://gitlab.com/some/other-repository/tags/1.0.1", } @@ -297,6 +309,7 @@ exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes Object { "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)", "name": undefined, + "notesSourceUrl": "https://api.gitlab.com/projects/some%2fother-repository/releases", "tag": "other-1.0.1", "url": "https://gitlab.com/some/other-repository/tags/other-1.0.1", } @@ -321,6 +334,7 @@ exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes Object { "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)", "name": undefined, + "notesSourceUrl": "https://api.gitlab.com/projects/some%2fother-repository/releases", "tag": "v1.0.1", "url": "https://gitlab.com/some/other-repository/tags/v1.0.1", } @@ -390,6 +404,7 @@ See merge request itentialopensource/adapter-utils!177 *** ", + "notesSourceUrl": "https://gitlab.com/itentialopensource/adapter-utils/blob/master/packages/foo/CHANGELOG.md", "url": "https://gitlab.com/itentialopensource/adapter-utils/blob/master/packages/foo/CHANGELOG.md#4330-05-15-2020", } `; @@ -428,6 +443,7 @@ See merge request itentialopensource/adapter-utils!177 *** ", + "notesSourceUrl": "https://gitlab.com/itentialopensource/adapter-utils/blob/master/CHANGELOG.md", "url": "https://gitlab.com/itentialopensource/adapter-utils/blob/master/CHANGELOG.md#4330-05-15-2020", } `; @@ -493,6 +509,7 @@ Object { - **translations:** fix pluralization in error messages. ([#1557](https://www.github.com/yargs/yargs/issues/1557)) ([94fa38c](https://www.github.com/yargs/yargs/commit/94fa38cbab8d86943e87bf41d368ed56dffa6835)) - **yargs:** correct support of bundled electron apps ([#1554](https://www.github.com/yargs/yargs/issues/1554)) ([a0b61ac](https://www.github.com/yargs/yargs/commit/a0b61ac21e2b554aa73dbf1a66d4a7af94047c2f)) ", + "notesSourceUrl": "https://github.com/yargs/yargs/blob/master/CHANGELOG.md", "url": "https://github.com/yargs/yargs/blob/master/CHANGELOG.md#1520-httpswwwgithubcomyargsyargscomparev1510v1520-2020-03-01", } `; @@ -543,6 +560,7 @@ Object { - address ambiguity between nargs of 1 and requiresArg ([#1572](https://www.github.com/yargs/yargs/issues/1572)) ([a5edc32](https://www.github.com/yargs/yargs/commit/a5edc328ecb3f90d1ba09cfe70a0040f68adf50a)) ", + "notesSourceUrl": "https://github.com/yargs/yargs/blob/master/CHANGELOG.md", "url": "https://github.com/yargs/yargs/blob/master/CHANGELOG.md#1530-httpswwwgithubcomyargsyargscomparev1520v1530-2020-03-08", } `; @@ -647,6 +665,7 @@ Object { "body": "- Fix \`condenseFlow\` output (quote keys for sure, instead of spaces), [#371](https://github.com/nodeca/js-yaml/issues/371), [#370](https://github.com/nodeca/js-yaml/issues/370). - Dump astrals as codepoints instead of surrogate pair, [#368](https://github.com/nodeca/js-yaml/issues/368). ", + "notesSourceUrl": "https://github.com/nodeca/js-yaml/blob/master/packages/foo/CHANGELOG.md", "url": "https://github.com/nodeca/js-yaml/blob/master/packages/foo/CHANGELOG.md#3100--2017-09-10", } `; @@ -712,6 +731,7 @@ Object { [#15085](https://github.com/angular/angular.js/issues/15085), [#15105](https://github.com/angular/angular.js/issues/15105)) ", + "notesSourceUrl": "https://github.com/angular/angular.js/blob/master/CHANGELOG.md", "url": "https://github.com/angular/angular.js/blob/master/CHANGELOG.md#169-fiery-basilisk-2018-02-02", } `; @@ -787,6 +807,7 @@ Object { - Update Polish translation, - Thanks to [@biesiad](https://gitlab.com/biesiad) for the contribution ", + "notesSourceUrl": "https://gitlab.com/gitlab-org/gitter/webapp/blob/master/CHANGELOG.md", "url": "https://gitlab.com/gitlab-org/gitter/webapp/blob/master/CHANGELOG.md#20260---2020-05-18", } `; @@ -1014,6 +1035,7 @@ Object { - \`[docs]\` Update \`expect.anything()\` sample code ([#5007](https://github.com/facebook/jest/pull/5007)) ", + "notesSourceUrl": "https://github.com/facebook/jest/blob/master/CHANGELOG.md", "url": "https://github.com/facebook/jest/blob/master/CHANGELOG.md#jest-2200", } `; @@ -1058,6 +1080,7 @@ Object { "body": "- Fix \`condenseFlow\` output (quote keys for sure, instead of spaces), [#371](https://github.com/nodeca/js-yaml/issues/371), [#370](https://github.com/nodeca/js-yaml/issues/370). - Dump astrals as codepoints instead of surrogate pair, [#368](https://github.com/nodeca/js-yaml/issues/368). ", + "notesSourceUrl": "https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md", "url": "https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md#3100--2017-09-10", } `; @@ -1100,6 +1123,7 @@ Object { - Update Polish translation, - Thanks to [@biesiad](https://gitlab.com/biesiad) for the contribution ", + "notesSourceUrl": "https://my.custom.domain/gitlab-org/gitter/webapp/blob/master/CHANGELOG.md", "url": "https://my.custom.domain/gitlab-org/gitter/webapp/blob/master/CHANGELOG.md#20260---2020-05-18", } `; diff --git a/lib/workers/pr/changelog/github/index.ts b/lib/workers/pr/changelog/github/index.ts index 354e7da27d6308..d73cc320940701 100644 --- a/lib/workers/pr/changelog/github/index.ts +++ b/lib/workers/pr/changelog/github/index.ts @@ -109,12 +109,13 @@ export async function getReleaseList( repository: string ): Promise { logger.trace('github.getReleaseList()'); - const url = `${ensureTrailingSlash( - apiBaseUrl - )}repos/${repository}/releases?per_page=100`; - const res = await http.getJson(url, { paginate: true }); + const url = `${ensureTrailingSlash(apiBaseUrl)}repos/${repository}/releases`; + const res = await http.getJson(`${url}?per_page=100`, { + paginate: true, + }); return res.body.map((release) => ({ url: release.html_url, + notesSourceUrl: url, id: release.id, tag: release.tag_name, name: release.name, diff --git a/lib/workers/pr/changelog/gitlab/index.ts b/lib/workers/pr/changelog/gitlab/index.ts index 3364cffb9e1a44..11217cb77e71ed 100644 --- a/lib/workers/pr/changelog/gitlab/index.ts +++ b/lib/workers/pr/changelog/gitlab/index.ts @@ -109,6 +109,7 @@ export async function getReleaseList( }); return res.body.map((release) => ({ url: `${apiUrl}/${release.tag_name}`, + notesSourceUrl: apiUrl, name: release.name, body: release.description, tag: release.tag_name, diff --git a/lib/workers/pr/changelog/hbs-template.ts b/lib/workers/pr/changelog/hbs-template.ts index 35521b9872be84..5de10f0658edbe 100644 --- a/lib/workers/pr/changelog/hbs-template.ts +++ b/lib/workers/pr/changelog/hbs-template.ts @@ -5,7 +5,7 @@ export default `### Release Notes {{#if upgrade.hasReleaseNotes}}
-{{upgrade.repoName}} +{{upgrade.releaseNotesSummaryTitle}} {{#each upgrade.releases as |release|}} diff --git a/lib/workers/pr/changelog/release-notes.spec.ts b/lib/workers/pr/changelog/release-notes.spec.ts index fbb145bc4c7122..d3af0fb71f76bf 100644 --- a/lib/workers/pr/changelog/release-notes.spec.ts +++ b/lib/workers/pr/changelog/release-notes.spec.ts @@ -75,11 +75,11 @@ describe('workers/pr/changelog/release-notes', () => { }); it('handles date object', () => { - expect(releaseNotesCacheMinutes(new Date())).toEqual(55); + expect(releaseNotesCacheMinutes(new Date())).toBe(55); }); it.each([null, undefined, 'fake', 123])('handles invalid: %s', (date) => { - expect(releaseNotesCacheMinutes(date as never)).toEqual(55); + expect(releaseNotesCacheMinutes(date as never)).toBe(55); }); }); diff --git a/lib/workers/pr/changelog/release-notes.ts b/lib/workers/pr/changelog/release-notes.ts index f365fde6e9a468..3f84a9cade2810 100644 --- a/lib/workers/pr/changelog/release-notes.ts +++ b/lib/workers/pr/changelog/release-notes.ts @@ -207,7 +207,9 @@ export async function getReleaseNotesMdFileInner( export function getReleaseNotesMdFile( project: ChangeLogProject ): Promise { - const cacheKey = `getReleaseNotesMdFile-${project.repository}-${project.apiBaseUrl}`; + const cacheKey = `getReleaseNotesMdFile@v2-${project.repository}${ + project.sourceDirectory ? `-${project.sourceDirectory}` : '' + }-${project.apiBaseUrl}`; const cachedResult = memCache.get>(cacheKey); // istanbul ignore if if (cachedResult !== undefined) { @@ -258,8 +260,11 @@ export async function getReleaseNotesMd( if (word.includes(version) && !isUrl(word)) { logger.trace({ body }, 'Found release notes for v' + version); // TODO: fix url - let url = `${baseUrl}${repository}/blob/master/${changelogFile}#`; - url += title.join('-').replace(regEx(/[^A-Za-z0-9-]/g), ''); // TODO #12071 + const notesSourceUrl = `${baseUrl}${repository}/blob/master/${changelogFile}`; + const url = + notesSourceUrl + + '#' + + title.join('-').replace(regEx(/[^A-Za-z0-9-]/g), ''); // TODO #12071 body = massageBody(body, baseUrl); if (body?.length) { try { @@ -273,6 +278,7 @@ export async function getReleaseNotesMd( return { body, url, + notesSourceUrl, }; } } @@ -320,10 +326,12 @@ export async function addReleaseNotes( return input; } const output: ChangeLogResult = { ...input, versions: [] }; - const repository = input.project.repository; - const cacheNamespace = `changelog-${input.project.type}-notes`; + const { repository, sourceDirectory } = input.project; + const cacheNamespace = `changelog-${input.project.type}-notes@v2`; function getCacheKey(version: string): string { - return `${repository}:${version}`; + return `${repository}:${ + sourceDirectory ? `${sourceDirectory}:` : '' + }${version}`; } for (const v of input.versions) { let releaseNotes: ChangeLogNotes; @@ -338,7 +346,7 @@ export async function addReleaseNotes( } // Small hack to force display of release notes when there is a compare url if (!releaseNotes && v.compare.url) { - releaseNotes = { url: v.compare.url }; + releaseNotes = { url: v.compare.url, notesSourceUrl: '' }; } const cacheMinutes = releaseNotesCacheMinutes(v.date); await packageCache.set( diff --git a/lib/workers/pr/changelog/types.ts b/lib/workers/pr/changelog/types.ts index 48217fc59eab65..8569d7015ca0cd 100644 --- a/lib/workers/pr/changelog/types.ts +++ b/lib/workers/pr/changelog/types.ts @@ -3,6 +3,8 @@ export interface ChangeLogNotes { id?: number; name?: string; tag?: string; + // url to changelog.md file or github/gitlab release api + notesSourceUrl: string; url: string; } diff --git a/lib/workers/pr/index.spec.ts b/lib/workers/pr/index.spec.ts index b511c9b5e2b296..37aba8e8b15482 100644 --- a/lib/workers/pr/index.spec.ts +++ b/lib/workers/pr/index.spec.ts @@ -214,14 +214,14 @@ describe('workers/pr/index', () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.red); config.prCreation = 'status-success'; const { prBlockedBy, pr } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('AwaitingTests'); + expect(prBlockedBy).toBe('AwaitingTests'); expect(pr).toBeUndefined(); }); it('should return needs-approval if prCreation set to approval', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.prCreation = 'approval'; const { prBlockedBy, pr } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('NeedsApproval'); + expect(prBlockedBy).toBe('NeedsApproval'); expect(pr).toBeUndefined(); }); it('should create PR if success for gitlab deps', async () => { @@ -265,7 +265,7 @@ describe('workers/pr/index', () => { config.schedule = ['before 5am']; limits.isLimitReached.mockReturnValueOnce(true); const { prBlockedBy } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('RateLimited'); + expect(prBlockedBy).toBe('RateLimited'); expect(platform.createPr.mock.calls).toBeEmpty(); }); it('should create PR if limit is reached but dashboard checked', async () => { @@ -282,6 +282,7 @@ describe('workers/pr/index', () => { expect(platform.createPr).toHaveBeenCalled(); }); it('should create group PR', async () => { + const depsWithSameNotesSourceUrl = ['e', 'f']; config.upgrades = config.upgrades.concat([ { depName: 'a', @@ -305,12 +306,44 @@ describe('workers/pr/index', () => { updateType: 'lockFileMaintenance', prBodyNotes: ['{{#if foo}}'], }, + { + depName: depsWithSameNotesSourceUrl[0], + updateType: 'lockFileMaintenance', + prBodyNotes: ['{{#if foo}}'], + }, + { + depName: depsWithSameNotesSourceUrl[1], + updateType: 'lockFileMaintenance', + prBodyNotes: ['{{#if foo}}'], + }, ] as never); config.updateType = 'lockFileMaintenance'; config.recreateClosed = true; config.rebaseWhen = 'never'; for (const upgrade of config.upgrades) { upgrade.logJSON = await changelogHelper.getChangeLogJSON(upgrade); + + if (depsWithSameNotesSourceUrl.includes(upgrade.depName)) { + upgrade.sourceDirectory = `packages/${upgrade.depName}`; + + upgrade.logJSON = { + ...upgrade.logJSON, + project: { + ...upgrade.logJSON.project, + repository: 'renovateapp/dummymonorepo', + }, + versions: upgrade.logJSON.versions.map((V) => { + return { + ...V, + releaseNotes: { + ...V.releaseNotes, + notesSourceUrl: + 'https://github.com/renovateapp/dummymonorepo/blob/changelogfile.md', + }, + }; + }), + }; + } } const { pr } = await prWorker.ensurePr(config); expect(pr).toMatchObject({ displayNumber: 'New Pull Request' }); @@ -341,7 +374,7 @@ describe('workers/pr/index', () => { }); config.prCreation = 'status-success'; const { prBlockedBy, pr } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('Error'); + expect(prBlockedBy).toBe('Error'); expect(pr).toBeUndefined(); }); it('should return null if waiting for not pending', async () => { @@ -351,7 +384,7 @@ describe('workers/pr/index', () => { ); config.prCreation = 'not-pending'; const { prBlockedBy, pr } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('AwaitingTests'); + expect(prBlockedBy).toBe('AwaitingTests'); expect(pr).toBeUndefined(); }); it('should not create PR if waiting for not pending with stabilityStatus yellow', async () => { @@ -362,7 +395,7 @@ describe('workers/pr/index', () => { config.prCreation = 'not-pending'; config.stabilityStatus = BranchStatus.yellow; const { prBlockedBy, pr } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('AwaitingTests'); + expect(prBlockedBy).toBe('AwaitingTests'); expect(pr).toBeUndefined(); }); it('should create PR if pending timeout hit', async () => { @@ -579,7 +612,7 @@ describe('workers/pr/index', () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); git.getBranchLastCommitTime.mockResolvedValueOnce(new Date()); const { prBlockedBy, pr } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('BranchAutomerge'); + expect(prBlockedBy).toBe('BranchAutomerge'); expect(pr).toBeUndefined(); }); it('should return PR if branch automerging taking too long', async () => { @@ -597,7 +630,7 @@ describe('workers/pr/index', () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); git.getBranchLastCommitTime.mockResolvedValueOnce(new Date('2018-01-01')); const { prBlockedBy, pr } = await prWorker.ensurePr(config); - expect(prBlockedBy).toEqual('BranchAutomerge'); + expect(prBlockedBy).toBe('BranchAutomerge'); expect(pr).toBeUndefined(); }); it('handles duplicate upgrades', async () => { diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts index 3825a54145a2d1..8ada26136c7f72 100644 --- a/lib/workers/pr/index.ts +++ b/lib/workers/pr/index.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { PLATFORM_INTEGRATION_UNAUTHORIZED, @@ -16,7 +16,7 @@ import { regEx } from '../../util/regex'; import * as template from '../../util/template'; import { resolveBranchStatus } from '../branch/status-checks'; import { Limit, incLimitedValue, isLimitReached } from '../global/limits'; -import type { BranchConfig, PrBlockedBy } from '../types'; +import type { BranchConfig, BranchUpgradeConfig, PrBlockedBy } from '../types'; import { getPrBody } from './body'; import { ChangeLogError } from './changelog/types'; import { codeOwnersForPr } from './code-owners'; @@ -70,7 +70,7 @@ export async function addAssigneesReviewers( } if (assignees.length > 0) { // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would add assignees to PR #${pr.number}`); } else { await platform.addAssignees(pr.number, assignees); @@ -99,7 +99,7 @@ export async function addAssigneesReviewers( } if (reviewers.length > 0) { // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would add reviewers to PR #${pr.number}`); } else { await platform.addReviewers(pr.number, reviewers); @@ -149,6 +149,16 @@ export type EnsurePrResult = ResultWithPr | ResultWithoutPr; export async function ensurePr( prConfig: BranchConfig ): Promise { + let branchStatus: BranchStatus; + async function getBranchStatus(): Promise { + if (branchStatus) { + return branchStatus; + } + branchStatus = await resolveBranchStatus(branchName, ignoreTests); + logger.debug(`Branch status is: ${branchStatus}`); + return branchStatus; + } + const config: BranchConfig = { ...prConfig }; logger.trace({ config }, 'ensurePr'); @@ -168,7 +178,6 @@ export async function ensurePr( logger.debug('Forcing PR because of artifact errors'); config.forcePr = true; } - let branchStatus: BranchStatus; // Only create a PR if a branch automerge has failed if ( @@ -176,13 +185,10 @@ export async function ensurePr( config.automergeType.startsWith('branch') && !config.forcePr ) { - branchStatus ||= await resolveBranchStatus(branchName, ignoreTests); - logger.debug( - `Branch is configured for branch automerge, branch status) is: ${branchStatus}` - ); + logger.debug(`Branch automerge is enabled`); if ( config.stabilityStatus !== BranchStatus.yellow && - branchStatus === BranchStatus.yellow + (await getBranchStatus()) === BranchStatus.yellow ) { logger.debug('Checking how long this branch has been pending'); const lastCommitTime = await getBranchLastCommitTime(branchName); @@ -196,7 +202,7 @@ export async function ensurePr( config.forcePr = true; } } - if (config.forcePr || branchStatus === BranchStatus.red) { + if (config.forcePr || (await getBranchStatus()) === BranchStatus.red) { logger.debug(`Branch tests failed, so will create PR`); } else { // Branch should be automerged, so we don't want to create a PR @@ -205,9 +211,8 @@ export async function ensurePr( } if (config.prCreation === 'status-success') { logger.debug('Checking branch combined status'); - branchStatus ||= await resolveBranchStatus(branchName, ignoreTests); - if (branchStatus !== BranchStatus.green) { - logger.debug(`Branch status is "${branchStatus}" - not creating PR`); + if ((await getBranchStatus()) !== BranchStatus.green) { + logger.debug(`Branch status isn't green - not creating PR`); return { prBlockedBy: 'AwaitingTests' }; } logger.debug('Branch status success'); @@ -223,9 +228,8 @@ export async function ensurePr( !config.forcePr ) { logger.debug('Checking branch combined status'); - branchStatus ||= await resolveBranchStatus(branchName, ignoreTests); - if (branchStatus === BranchStatus.yellow) { - logger.debug(`Branch status is "${branchStatus}" - checking timeout`); + if ((await getBranchStatus()) === BranchStatus.yellow) { + logger.debug(`Branch status is yellow - checking timeout`); const lastCommitTime = await getBranchLastCommitTime(branchName); const currentTime = new Date(); const millisecondsPerHour = 1000 * 60 * 60; @@ -256,13 +260,21 @@ export async function ensurePr( const processedUpgrades: string[] = []; const commitRepos: string[] = []; + function getRepoNameWithSourceDirectory( + upgrade: BranchUpgradeConfig + ): string { + return `${upgrade.repoName}${ + upgrade.sourceDirectory ? `:${upgrade.sourceDirectory}` : '' + }`; + } + // Get changelog and then generate template strings for (const upgrade of upgrades) { const upgradeKey = `${upgrade.depType}-${upgrade.depName}-${ upgrade.manager }-${upgrade.currentVersion || upgrade.currentValue}-${upgrade.newVersion}`; if (processedUpgrades.includes(upgradeKey)) { - continue; // eslint-disable-line no-continue + continue; } processedUpgrades.push(upgradeKey); @@ -278,9 +290,9 @@ export async function ensurePr( if ( upgrade.hasReleaseNotes && upgrade.repoName && - !commitRepos.includes(upgrade.repoName) + !commitRepos.includes(getRepoNameWithSourceDirectory(upgrade)) ) { - commitRepos.push(upgrade.repoName); + commitRepos.push(getRepoNameWithSourceDirectory(upgrade)); if (logJSON.versions) { logJSON.versions.forEach((version) => { const release = { ...version }; @@ -305,17 +317,20 @@ export async function ensurePr( config.hasReleaseNotes = config.upgrades.some((upg) => upg.hasReleaseNotes); - const releaseNoteRepos: string[] = []; + const releaseNotesSources: string[] = []; for (const upgrade of config.upgrades) { - if (upgrade.hasReleaseNotes) { - if (releaseNoteRepos.includes(upgrade.sourceUrl)) { + const notesSourceUrl = + upgrade.releases?.[0]?.releaseNotes?.notesSourceUrl || upgrade.sourceUrl; + + if (upgrade.hasReleaseNotes && notesSourceUrl) { + if (releaseNotesSources.includes(notesSourceUrl)) { logger.debug( { depName: upgrade.depName }, 'Removing duplicate release notes' ); upgrade.hasReleaseNotes = false; } else { - releaseNoteRepos.push(upgrade.sourceUrl); + releaseNotesSources.push(notesSourceUrl); } } } @@ -330,7 +345,7 @@ export async function ensurePr( !existingPr.hasAssignees && !existingPr.hasReviewers && config.automerge && - branchStatus === BranchStatus.red + (await getBranchStatus()) === BranchStatus.red ) { logger.debug(`Setting assignees and reviewers as status checks failed`); await addAssigneesReviewers(config, existingPr); @@ -374,7 +389,7 @@ export async function ensurePr( ); } // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would update PR #${existingPr.number}`); } else { await platform.updatePr({ @@ -397,7 +412,7 @@ export async function ensurePr( let pr: Pr; try { // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would create PR: ' + prTitle); pr = { number: 0, displayNumber: 'Dry run PR' } as never; } else { @@ -440,7 +455,7 @@ export async function ensurePr( { branch: branchName }, 'Deleting branch due to server error' ); - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would delete branch: ' + config.branchName); } else { await deleteBranch(branchName); @@ -461,7 +476,7 @@ export async function ensurePr( content = platform.massageMarkdown(content); logger.debug('Adding branch automerge failure message to PR'); // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would add comment to PR #${pr.number}`); } else { await platform.ensureComment({ @@ -475,7 +490,7 @@ export async function ensurePr( if ( config.automerge && !config.assignAutomerge && - branchStatus !== BranchStatus.red + (await getBranchStatus()) !== BranchStatus.red ) { logger.debug( `Skipping assignees and reviewers as automerge=${config.automerge}` diff --git a/lib/workers/repository/__snapshots__/dependency-dashboard.spec.ts.snap b/lib/workers/repository/__snapshots__/dependency-dashboard.spec.ts.snap index 02d286d9fd624d..73af65201bc704 100644 --- a/lib/workers/repository/__snapshots__/dependency-dashboard.spec.ts.snap +++ b/lib/workers/repository/__snapshots__/dependency-dashboard.spec.ts.snap @@ -59,15 +59,3 @@ These updates are awaiting their schedule. Click on a checkbox to get an update " `; - -exports[`workers/repository/dependency-dashboard readDashboardBody() reads dashboard body 1`] = ` -Object { - "dependencyDashboardChecks": Object { - "branchName1": "approve", - }, - "dependencyDashboardIssue": 1, - "dependencyDashboardRebaseAllOpen": true, - "dependencyDashboardTitle": "Dependency Dashboard", - "prCreation": "approval", -} -`; diff --git a/lib/workers/repository/__snapshots__/index.spec.ts.snap b/lib/workers/repository/__snapshots__/index.spec.ts.snap deleted file mode 100644 index b51587e46bcac5..00000000000000 --- a/lib/workers/repository/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/index renovateRepository() runs 1`] = `undefined`; diff --git a/lib/workers/repository/cache.ts b/lib/workers/repository/cache.ts index e8f9c4bcd51113..5ef775c4a1ace9 100644 --- a/lib/workers/repository/cache.ts +++ b/lib/workers/repository/cache.ts @@ -75,13 +75,12 @@ async function generateBranchCache(branch: BranchConfig): Promise { isModified, upgrades, }; - } catch (err) { + } catch (error) { + const err = error.err || error; // external host error nests err + const errCodes = [401, 404]; // istanbul ignore if - if (err.response?.statusCode === 404) { - logger.warn( - { err, branchName }, - '404 error when generating branch cache' - ); + if (errCodes.includes(err.response?.statusCode)) { + logger.warn({ err, branchName }, 'HTTP error generating branch cache'); return null; } logger.error({ err, branchName }, 'Error generating branch cache'); diff --git a/lib/workers/repository/changelog/index.ts b/lib/workers/repository/changelog/index.ts index b0c0697b1a9ec0..feff7e56e5d9b8 100644 --- a/lib/workers/repository/changelog/index.ts +++ b/lib/workers/repository/changelog/index.ts @@ -4,7 +4,7 @@ import type { BranchUpgradeConfig } from '../../types'; // istanbul ignore next async function embedChangelog(upgrade: BranchUpgradeConfig): Promise { - upgrade.logJSON = await getChangeLogJSON(upgrade); // eslint-disable-line + upgrade.logJSON = await getChangeLogJSON(upgrade); } // istanbul ignore next diff --git a/lib/workers/repository/dependency-dashboard.spec.ts b/lib/workers/repository/dependency-dashboard.spec.ts index ef16629b7a969c..94b4edd93a8415 100644 --- a/lib/workers/repository/dependency-dashboard.spec.ts +++ b/lib/workers/repository/dependency-dashboard.spec.ts @@ -7,7 +7,7 @@ import { logger, platform, } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { PlatformId } from '../../constants'; import type { Platform } from '../../platform'; import { BranchConfig, BranchResult, BranchUpgradeConfig } from '../types'; @@ -26,13 +26,13 @@ beforeEach(() => { async function dryRun( branches: BranchConfig[], - // eslint-disable-next-line @typescript-eslint/no-shadow + platform: jest.Mocked, ensureIssueClosingCalls = 0, ensureIssueCalls = 0 ) { jest.clearAllMocks(); - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); await dependencyDashboard.ensureDependencyDashboard(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes( ensureIssueClosingCalls @@ -53,14 +53,21 @@ describe('workers/repository/dependency-dashboard', () => { '\n\n - [x] ', }); await dependencyDashboard.readDashboardBody(conf); - // FIXME: explicit assert condition - expect(conf).toMatchSnapshot(); + expect(conf).toEqual({ + dependencyDashboardChecks: { + branchName1: 'approve', + }, + dependencyDashboardIssue: 1, + dependencyDashboardRebaseAllOpen: true, + dependencyDashboardTitle: 'Dependency Dashboard', + prCreation: 'approval', + }); }); }); describe('ensureDependencyDashboard()', () => { beforeEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('do nothing if dependencyDashboard is disabled', async () => { const branches: BranchConfig[] = []; diff --git a/lib/workers/repository/dependency-dashboard.ts b/lib/workers/repository/dependency-dashboard.ts index 6dc1dded4dcd2f..7953327c40e64b 100644 --- a/lib/workers/repository/dependency-dashboard.ts +++ b/lib/workers/repository/dependency-dashboard.ts @@ -1,6 +1,6 @@ import is from '@sindresorhus/is'; import { nameFromLevel } from 'bunyan'; -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { getProblems, logger } from '../../logger'; import { platform } from '../../platform'; @@ -29,13 +29,11 @@ function parseDashboardIssue(issueBody: string): DependencyDashboard { let dependencyDashboardRebaseAllOpen = false; if (checkedRebaseAll) { dependencyDashboardRebaseAllOpen = true; - /* eslint-enable no-param-reassign */ } return { dependencyDashboardChecks, dependencyDashboardRebaseAllOpen }; } export async function readDashboardBody(config: RenovateConfig): Promise { - /* eslint-disable no-param-reassign */ config.dependencyDashboardChecks = {}; const stringifiedConfig = JSON.stringify(config); if ( @@ -51,7 +49,6 @@ export async function readDashboardBody(config: RenovateConfig): Promise { Object.assign(config, parseDashboardIssue(issue.body)); } } - /* eslint-enable no-param-reassign */ } function getListItem(branch: BranchConfig, type: string): string { @@ -114,7 +111,7 @@ export async function ensureDependencyDashboard( ) ) ) { - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( { title: config.dependencyDashboardTitle }, 'DRY-RUN: Would close Dependency Dashboard' @@ -135,7 +132,7 @@ export async function ensureDependencyDashboard( is.nonEmptyArray(branches) && branches.some((branch) => branch.result !== BranchResult.Automerged); if (config.dependencyDashboardAutoclose && !hasBranches) { - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( { title: config.dependencyDashboardTitle }, 'DRY-RUN: Would close Dependency Dashboard' @@ -348,7 +345,7 @@ export async function ensureDependencyDashboard( } } - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info( { title: config.dependencyDashboardTitle }, 'DRY-RUN: Would ensure Dependency Dashboard' @@ -359,6 +356,7 @@ export async function ensureDependencyDashboard( reuseTitle, body: issueBody, labels: config.dependencyDashboardLabels, + confidential: config.confidential, }); } } diff --git a/lib/workers/repository/error-config.spec.ts b/lib/workers/repository/error-config.spec.ts index 6f791725ce62b8..f940702b30c259 100644 --- a/lib/workers/repository/error-config.spec.ts +++ b/lib/workers/repository/error-config.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; import { RenovateConfig, getConfig, platform } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import { CONFIG_VALIDATION } from '../../constants/error-messages'; import { Pr } from '../../platform'; import { PrState } from '../../types'; @@ -17,7 +17,7 @@ beforeEach(() => { describe('workers/repository/error-config', () => { describe('raiseConfigWarningIssue()', () => { beforeEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('creates issues', async () => { const error = new Error(CONFIG_VALIDATION); @@ -32,7 +32,7 @@ describe('workers/repository/error-config', () => { error.validationSource = 'package.json'; error.validationMessage = 'some-message'; platform.ensureIssue.mockResolvedValueOnce('created'); - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); const res = await raiseConfigWarningIssue(config, error); expect(res).toBeUndefined(); }); @@ -57,7 +57,7 @@ describe('workers/repository/error-config', () => { number: 1, state: PrState.Open, }); - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); const res = await raiseConfigWarningIssue(config, error); expect(res).toBeUndefined(); }); diff --git a/lib/workers/repository/error-config.ts b/lib/workers/repository/error-config.ts index 5b10967bac489b..98601171f9e250 100644 --- a/lib/workers/repository/error-config.ts +++ b/lib/workers/repository/error-config.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { logger } from '../../logger'; import { platform } from '../../platform'; @@ -26,7 +26,7 @@ export async function raiseConfigWarningIssue( logger.debug('Updating onboarding PR with config error notice'); body = `## Action Required: Fix Renovate Configuration\n\n${body}`; body += `\n\nOnce you have resolved this problem (in this onboarding branch), Renovate will return to providing you with a preview of your repository's configuration.`; - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would update PR #${pr.number}`); } else { try { @@ -39,7 +39,7 @@ export async function raiseConfigWarningIssue( logger.warn({ err }, 'Error updating onboarding PR'); } } - } else if (getGlobalConfig().dryRun) { + } else if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would ensure config error issue'); } else { const once = false; @@ -49,6 +49,7 @@ export async function raiseConfigWarningIssue( body, once, shouldReOpen: shouldReopen, + confidential: config.confidential, }); if (res === 'created') { logger.warn({ configError: error, res }, 'Config Warning'); diff --git a/lib/workers/repository/error.ts b/lib/workers/repository/error.ts index 65aa292b753023..381cb6ed10ca2b 100644 --- a/lib/workers/repository/error.ts +++ b/lib/workers/repository/error.ts @@ -41,12 +41,12 @@ export default async function handleError( ): Promise { if (err.message === REPOSITORY_UNINITIATED) { logger.info('Repository is uninitiated - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === REPOSITORY_EMPTY) { logger.info('Repository is empty - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } const disabledMessages = [ @@ -61,31 +61,31 @@ export default async function handleError( } if (err.message === REPOSITORY_ARCHIVED) { logger.info('Repository is archived - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === REPOSITORY_MIRRORED) { logger.info('Repository is a mirror - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === REPOSITORY_RENAMED) { logger.info('Repository has been renamed - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === REPOSITORY_BLOCKED) { - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; logger.info('Repository is blocked - skipping'); return err.message; } if (err.message === REPOSITORY_ACCESS_FORBIDDEN) { - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; logger.info('Repository is forbidden'); return err.message; } if (err.message === REPOSITORY_NOT_FOUND) { - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; logger.error('Repository is not found'); return err.message; } @@ -107,17 +107,17 @@ export default async function handleError( } if (err.message === REPOSITORY_CHANGED) { logger.info('Repository has changed during renovation - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === CONFIG_VALIDATION) { - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; logger.info({ error: err }, 'Repository has invalid config'); await raiseConfigWarningIssue(config, err); return err.message; } if (err.message === CONFIG_SECRETS_EXPOSED) { - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; logger.warn( { error: err }, 'Repository aborted due to potential secrets exposure' @@ -130,7 +130,7 @@ export default async function handleError( 'Host error' ); logger.info('External host error causing abort - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if ( @@ -138,48 +138,48 @@ export default async function handleError( err.message === SYSTEM_INSUFFICIENT_DISK_SPACE ) { logger.error('Disk space error - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === PLATFORM_RATE_LIMIT_EXCEEDED) { logger.warn('Rate limit exceeded - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === SYSTEM_INSUFFICIENT_MEMORY) { logger.warn('Insufficient memory - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === PLATFORM_BAD_CREDENTIALS) { logger.warn('Bad credentials - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === PLATFORM_INTEGRATION_UNAUTHORIZED) { logger.warn('Integration unauthorized - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === PLATFORM_AUTHENTICATION_ERROR) { logger.warn('Authentication error - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === TEMPORARY_ERROR) { logger.info('Temporary error - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message === MANAGER_LOCKFILE_ERROR) { - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; logger.info('Lock file error - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return err.message; } if (err.message.includes('The requested URL returned error: 5')) { logger.warn({ err }, 'Git error - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; // rewrite this error return EXTERNAL_HOST_ERROR; } @@ -188,18 +188,18 @@ export default async function handleError( err.message.includes('access denied or repository not exported') ) { logger.warn({ err }, 'Git error - aborting'); - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; // rewrite this error return EXTERNAL_HOST_ERROR; } if (err.message.includes('fatal: not a git repository')) { - delete config.branchList; // eslint-disable-line no-param-reassign + delete config.branchList; return TEMPORARY_ERROR; } // Swallow this error so that other repositories can be processed logger.error({ err }, `Repository has unknown error`); // delete branchList to avoid cleaning up branches - delete config.branchList; // eslint-disable-line no-param-reassign - // eslint-disable-next-line no-undef + delete config.branchList; + return UNKNOWN_ERROR; } diff --git a/lib/workers/repository/extract/__snapshots__/index.spec.ts.snap b/lib/workers/repository/extract/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 310997713ce6df..00000000000000 --- a/lib/workers/repository/extract/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/extract/index extractAllDependencies() skips non-enabled managers 1`] = ` -Object { - "npm": Array [ - Object {}, - ], -} -`; diff --git a/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap b/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap index 281f2250844f50..125d5e754a4b09 100644 --- a/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap +++ b/lib/workers/repository/extract/__snapshots__/manager-files.spec.ts.snap @@ -33,20 +33,3 @@ Array [ }, ] `; - -exports[`workers/repository/extract/manager-files getManagerPackageFiles() returns files with extractPackageFile 1`] = ` -Array [ - Object { - "deps": Array [ - Object { - "depIndex": 0, - }, - Object { - "depIndex": 1, - "replaceString": "abc", - }, - ], - "packageFile": "Dockerfile", - }, -] -`; diff --git a/lib/workers/repository/extract/index.spec.ts b/lib/workers/repository/extract/index.spec.ts index 2e75237e523db3..f7afcf2bc2d548 100644 --- a/lib/workers/repository/extract/index.spec.ts +++ b/lib/workers/repository/extract/index.spec.ts @@ -27,8 +27,7 @@ describe('workers/repository/extract/index', () => { config.enabledManagers = ['npm']; managerFiles.getManagerPackageFiles.mockResolvedValue([{} as never]); const res = await extractAllDependencies(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toEqual({ npm: [{}] }); }); it('warns if no packages found for a enabled manager', async () => { diff --git a/lib/workers/repository/extract/manager-files.spec.ts b/lib/workers/repository/extract/manager-files.spec.ts index c83f5a55f7ad60..79f5b14cbee4ee 100644 --- a/lib/workers/repository/extract/manager-files.spec.ts +++ b/lib/workers/repository/extract/manager-files.spec.ts @@ -47,8 +47,12 @@ describe('workers/repository/extract/manager-files', () => { deps: [{}, { replaceString: 'abc' }], })) as never; const res = await getManagerPackageFiles(managerConfig); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toEqual([ + { + packageFile: 'Dockerfile', + deps: [{ depIndex: 0 }, { depIndex: 1, replaceString: 'abc' }], + }, + ]); }); it('returns files with extractAllPackageFiles', async () => { const managerConfig = { @@ -61,8 +65,20 @@ describe('workers/repository/extract/manager-files', () => { '{"dependencies":{"chalk":"2.0.0"}}' ); const res = await getManagerPackageFiles(managerConfig); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot([ + { + packageFile: 'package.json', + packageJsonType: 'app', + deps: [ + { + currentValue: '2.0.0', + datasource: 'npm', + depName: 'chalk', + depType: 'dependencies', + }, + ], + }, + ]); }); }); }); diff --git a/lib/workers/repository/finalise/prune.spec.ts b/lib/workers/repository/finalise/prune.spec.ts index 6e2c0c7418e180..a57909be0e50c8 100644 --- a/lib/workers/repository/finalise/prune.spec.ts +++ b/lib/workers/repository/finalise/prune.spec.ts @@ -4,7 +4,7 @@ import { git, platform, } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { PlatformId } from '../../../constants'; import * as cleanup from './prune'; @@ -22,7 +22,7 @@ beforeEach(() => { describe('workers/repository/finalise/prune', () => { describe('pruneStaleBranches()', () => { beforeEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); it('returns if no branchList', async () => { delete config.branchList; @@ -69,7 +69,7 @@ describe('workers/repository/finalise/prune', () => { }); it('does nothing on dryRun', async () => { config.branchList = ['renovate/a', 'renovate/b']; - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); git.getBranchList.mockReturnValueOnce( config.branchList.concat(['renovate/c']) ); @@ -109,7 +109,7 @@ describe('workers/repository/finalise/prune', () => { }); it('skips comment if dry run', async () => { config.branchList = ['renovate/a', 'renovate/b']; - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); git.getBranchList.mockReturnValueOnce( config.branchList.concat(['renovate/c']) ); @@ -124,7 +124,7 @@ describe('workers/repository/finalise/prune', () => { }); it('dry run delete branch no PR', async () => { config.branchList = ['renovate/a', 'renovate/b']; - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); git.getBranchList.mockReturnValueOnce( config.branchList.concat(['renovate/c']) ); diff --git a/lib/workers/repository/finalise/prune.ts b/lib/workers/repository/finalise/prune.ts index 8d2577a6c7f209..17aaf3bd05d235 100644 --- a/lib/workers/repository/finalise/prune.ts +++ b/lib/workers/repository/finalise/prune.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import type { RenovateConfig } from '../../../config/types'; import { REPOSITORY_CHANGED } from '../../../constants/error-messages'; import { logger } from '../../../logger'; @@ -31,7 +31,7 @@ async function cleanUpBranches( { prNo: pr.number, prTitle: pr.title }, 'Branch is modified - skipping PR autoclosing' ); - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would add Autoclosing Skipped comment to PR`); } else { await platform.ensureComment({ @@ -41,7 +41,7 @@ async function cleanUpBranches( 'This PR has been flagged for autoclosing, however it is being skipped due to the branch being already modified. Please close/delete it manually or report a bug if you think this is in error.', }); } - } else if (getGlobalConfig().dryRun) { + } else if (GlobalConfig.get('dryRun')) { logger.info( { prNo: pr.number, prTitle: pr.title }, `DRY-RUN: Would autoclose PR` @@ -62,14 +62,18 @@ async function cleanUpBranches( }); await deleteBranch(branchName); } - } else if (getGlobalConfig().dryRun) { + } else if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would delete orphan branch ${branchName}`); } else { logger.info({ branch: branchName }, `Deleting orphan branch`); await deleteBranch(branchName); } } catch (err) /* istanbul ignore next */ { - if (err.message?.includes("bad revision 'origin/")) { + if (err.message === 'config-validation') { + logger.debug( + 'Cannot prune branch due to collision between tags and branch names' + ); + } else if (err.message?.includes("bad revision 'origin/")) { logger.debug( { branchName }, 'Branch not found on origin when attempting to prune' diff --git a/lib/workers/repository/index.spec.ts b/lib/workers/repository/index.spec.ts index 28fda468626ce3..e45c1b78ef2f6d 100644 --- a/lib/workers/repository/index.spec.ts +++ b/lib/workers/repository/index.spec.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; import { RenovateConfig, getConfig, mocked } from '../../../test/util'; -import { setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import * as _process from './process'; import { ExtractResult } from './process/extract-update'; import { renovateRepository } from '.'; @@ -18,13 +18,12 @@ describe('workers/repository/index', () => { let config: RenovateConfig; beforeEach(() => { config = getConfig(); - setGlobalConfig({ localDir: '' }); + GlobalConfig.set({ localDir: '' }); }); it('runs', async () => { process.extractDependencies.mockResolvedValue(mock()); const res = await renovateRepository(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toBeUndefined(); }); }); }); diff --git a/lib/workers/repository/index.ts b/lib/workers/repository/index.ts index 5005a9ee5e36bf..1357f316557f86 100644 --- a/lib/workers/repository/index.ts +++ b/lib/workers/repository/index.ts @@ -1,5 +1,5 @@ import fs from 'fs-extra'; -import { getGlobalConfig, setGlobalConfig } from '../../config/global'; +import { GlobalConfig } from '../../config/global'; import type { RenovateConfig } from '../../config/types'; import { logger, setMeta } from '../../logger'; import { removeDanglingContainers } from '../../util/exec/docker'; @@ -19,7 +19,7 @@ import { printRequestStats } from './stats'; let renovateVersion = 'unknown'; try { // eslint-disable-next-line @typescript-eslint/no-var-requires - renovateVersion = require('../../../package.json').version; // eslint-disable-line global-require + renovateVersion = require('../../../package.json').version; } catch (err) /* istanbul ignore next */ { logger.debug({ err }, 'Error getting renovate version'); } @@ -30,14 +30,14 @@ export async function renovateRepository( canRetry = true ): Promise { splitInit(); - let config = setGlobalConfig(repoConfig); + let config = GlobalConfig.set(repoConfig); await removeDanglingContainers(); setMeta({ repository: config.repository }); logger.info({ renovateVersion }, 'Repository started'); logger.trace({ config }); let repoResult: ProcessResult; queue.clear(); - const { localDir } = getGlobalConfig(); + const { localDir } = GlobalConfig.get(); try { await fs.ensureDir(localDir); logger.debug('Using localDir: ' + localDir); diff --git a/lib/workers/repository/init/__snapshots__/index.spec.ts.snap b/lib/workers/repository/init/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 802c8eb539672e..00000000000000 --- a/lib/workers/repository/init/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/init/index initRepo runs 1`] = `Object {}`; diff --git a/lib/workers/repository/init/__snapshots__/merge.spec.ts.snap b/lib/workers/repository/init/__snapshots__/merge.spec.ts.snap deleted file mode 100644 index 2271e01caa04df..00000000000000 --- a/lib/workers/repository/init/__snapshots__/merge.spec.ts.snap +++ /dev/null @@ -1,76 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/init/merge detectRepoFileConfig() finds .github/renovate.json 1`] = ` -Object { - "configFileName": ".github/renovate.json", - "configFileParsed": Object {}, -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() finds .gitlab/renovate.json 1`] = ` -Object { - "configFileName": ".gitlab/renovate.json", - "configFileParsed": Object {}, -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() finds .renovaterc.json 1`] = ` -Object { - "configFileName": ".renovaterc.json", - "configFileParsed": Object {}, -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() finds .renovaterc.json 2`] = ` -Object { - "configFileName": ".renovaterc.json", - "configFileParsed": "{\\"something\\":\\"new\\"}", -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() finds and parse renovate.json5 1`] = ` -Object { - "configFileName": "renovate.json5", - "configFileParsed": Object {}, -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() returns config if not found 1`] = `Object {}`; - -exports[`workers/repository/init/merge detectRepoFileConfig() returns error if cannot parse 1`] = ` -Object { - "configFileName": "renovate.json", - "configFileParseError": Object { - "validationError": "Invalid JSON (parsing failed)", - "validationMessage": "Syntax error near cannot par", - }, -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() throws error if duplicate keys 1`] = ` -Object { - "configFileName": ".renovaterc", - "configFileParseError": Object { - "validationError": "Duplicate keys in JSON", - "validationMessage": "\\"Syntax error: duplicated keys \\\\\\"enabled\\\\\\" near \\\\\\": false }\\"", - }, -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() uses package.json config if found 1`] = ` -Object { - "configFileName": "package.json", - "configFileParsed": Object { - "prHourlyLimit": 10, - }, -} -`; - -exports[`workers/repository/init/merge detectRepoFileConfig() uses package.json config if found 2`] = ` -Object { - "configFileName": "package.json", - "configFileParsed": undefined, -} -`; - -exports[`workers/repository/init/merge mergeRenovateConfig() throws error if misconfigured 1`] = `[Error: config-validation]`; diff --git a/lib/workers/repository/init/cache.spec.ts b/lib/workers/repository/init/cache.spec.ts index bb3de366992a89..9f06b635a18c7a 100644 --- a/lib/workers/repository/init/cache.spec.ts +++ b/lib/workers/repository/init/cache.spec.ts @@ -1,5 +1,5 @@ import { RenovateConfig, getConfig } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import { initializeCaches } from './cache'; describe('workers/repository/init/cache', () => { @@ -7,7 +7,7 @@ describe('workers/repository/init/cache', () => { let config: RenovateConfig; beforeEach(() => { config = { ...getConfig() }; - setGlobalConfig({ cacheDir: '' }); + GlobalConfig.set({ cacheDir: '' }); }); it('initializes', async () => { expect(await initializeCaches(config)).toBeUndefined(); diff --git a/lib/workers/repository/init/index.spec.ts b/lib/workers/repository/init/index.spec.ts index 16b7e0bbe36987..a166c3d70219c3 100644 --- a/lib/workers/repository/init/index.spec.ts +++ b/lib/workers/repository/init/index.spec.ts @@ -1,5 +1,5 @@ import { logger, mocked } from '../../../../test/util'; -import { setGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import * as _secrets from '../../../config/secrets'; import * as _onboarding from '../onboarding/branch'; import * as _apis from './apis'; @@ -24,10 +24,10 @@ const secrets = mocked(_secrets); describe('workers/repository/init/index', () => { beforeEach(() => { - setGlobalConfig({ localDir: '', cacheDir: '' }); + GlobalConfig.set({ localDir: '', cacheDir: '' }); }); afterEach(() => { - setGlobalConfig(); + GlobalConfig.reset(); }); describe('initRepo', () => { @@ -38,8 +38,7 @@ describe('workers/repository/init/index', () => { merge.mergeRenovateConfig.mockResolvedValueOnce({}); secrets.applySecretsToConfig.mockReturnValueOnce({} as never); const renovateConfig = await initRepo({}); - // FIXME: explicit assert condition - expect(renovateConfig).toMatchSnapshot(); + expect(renovateConfig).toEqual({}); }); it('warns on unsupported options', async () => { apis.initApis.mockResolvedValue({} as never); diff --git a/lib/workers/repository/init/merge.spec.ts b/lib/workers/repository/init/merge.spec.ts index 937ef640628ec3..36c7a4076dc9b4 100644 --- a/lib/workers/repository/init/merge.spec.ts +++ b/lib/workers/repository/init/merge.spec.ts @@ -41,8 +41,7 @@ describe('workers/repository/init/merge', () => { it('returns config if not found', async () => { git.getFileList.mockResolvedValue(['package.json']); fs.readLocalFile.mockResolvedValue('{}'); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({}); }); it('uses package.json config if found', async () => { git.getFileList.mockResolvedValue(['package.json']); @@ -54,9 +53,14 @@ describe('workers/repository/init/merge', () => { }); fs.readLocalFile.mockResolvedValue(pJson); platform.getJsonFile.mockResolvedValueOnce(pJson); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: 'package.json', + configFileParsed: { prHourlyLimit: 10 }, + }); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: 'package.json', + configFileParsed: undefined, + }); }); it('massages package.json renovate string', async () => { git.getFileList.mockResolvedValue(['package.json']); @@ -66,38 +70,45 @@ describe('workers/repository/init/merge', () => { }); fs.readLocalFile.mockResolvedValue(pJson); platform.getJsonFile.mockResolvedValueOnce(pJson); - expect(await detectRepoFileConfig()).toMatchInlineSnapshot(` - Object { - "configFileName": "package.json", - "configFileParsed": Object { - "extends": Array [ - "github>renovatebot/renovate", - ], - }, - } - `); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: 'package.json', + configFileParsed: { extends: ['github>renovatebot/renovate'] }, + }); }); it('returns error if cannot parse', async () => { git.getFileList.mockResolvedValue(['package.json', 'renovate.json']); fs.readLocalFile.mockResolvedValue('cannot parse'); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: 'renovate.json', + configFileParseError: { + validationError: 'Invalid JSON (parsing failed)', + validationMessage: 'Syntax error near cannot par', + }, + }); }); it('throws error if duplicate keys', async () => { git.getFileList.mockResolvedValue(['package.json', '.renovaterc']); fs.readLocalFile.mockResolvedValue( '{ "enabled": true, "enabled": false }' ); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: '.renovaterc', + configFileParseError: { + validationError: 'Duplicate keys in JSON', + validationMessage: + '"Syntax error: duplicated keys \\"enabled\\" near \\": false }"', + }, + }); }); it('finds and parse renovate.json5', async () => { git.getFileList.mockResolvedValue(['package.json', 'renovate.json5']); fs.readLocalFile.mockResolvedValue(`{ // this is json5 format }`); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: 'renovate.json5', + configFileParsed: {}, + }); }); it('finds .github/renovate.json', async () => { git.getFileList.mockResolvedValue([ @@ -105,8 +116,10 @@ describe('workers/repository/init/merge', () => { '.github/renovate.json', ]); fs.readLocalFile.mockResolvedValue('{}'); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: '.github/renovate.json', + configFileParsed: {}, + }); }); it('finds .gitlab/renovate.json', async () => { git.getFileList.mockResolvedValue([ @@ -114,16 +127,25 @@ describe('workers/repository/init/merge', () => { '.gitlab/renovate.json', ]); fs.readLocalFile.mockResolvedValue('{}'); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: '.gitlab/renovate.json', + configFileParsed: {}, + }); }); it('finds .renovaterc.json', async () => { git.getFileList.mockResolvedValue(['package.json', '.renovaterc.json']); fs.readLocalFile.mockResolvedValue('{}'); platform.getJsonFile.mockResolvedValueOnce('{"something":"new"}'); - // FIXME: explicit assert condition - expect(await detectRepoFileConfig()).toMatchSnapshot(); - expect(await detectRepoFileConfig()).toMatchSnapshot(); + expect(await detectRepoFileConfig()).toEqual({ + configFileName: '.renovaterc.json', + configFileParsed: {}, + }); + expect(await detectRepoFileConfig()).toMatchInlineSnapshot(` + Object { + "configFileName": ".renovaterc.json", + "configFileParsed": "{\\"something\\":\\"new\\"}", + } + `); }); }); describe('checkForRepoConfigError', () => { @@ -158,8 +180,7 @@ describe('workers/repository/init/merge', () => { e = err; } expect(e).toBeDefined(); - // FIXME: explicit assert condition - expect(e).toMatchSnapshot(); + expect(e.toString()).toBe('Error: config-validation'); }); it('migrates nested config', async () => { git.getFileList.mockResolvedValue(['renovate.json']); @@ -173,7 +194,7 @@ describe('workers/repository/init/merge', () => { migratedConfig: {}, }); config.extends = [':automergeDisabled']; - expect(await mergeRenovateConfig(config)).not.toBeUndefined(); + expect(await mergeRenovateConfig(config)).toBeDefined(); }); it('continues if no errors', async () => { git.getFileList.mockResolvedValue(['package.json', '.renovaterc.json']); @@ -183,7 +204,7 @@ describe('workers/repository/init/merge', () => { errors: [], }); config.extends = [':automergeDisabled']; - expect(await mergeRenovateConfig(config)).not.toBeUndefined(); + expect(await mergeRenovateConfig(config)).toBeDefined(); }); }); }); diff --git a/lib/workers/repository/init/vulnerability.spec.ts b/lib/workers/repository/init/vulnerability.spec.ts index c894e046eb5d48..2bef7df68734d3 100644 --- a/lib/workers/repository/init/vulnerability.spec.ts +++ b/lib/workers/repository/init/vulnerability.spec.ts @@ -289,8 +289,16 @@ describe('workers/repository/init/vulnerability', () => { const res = await detectVulnerabilityAlerts(config); expect(res.packageRules).toMatchSnapshot(); expect(res.packageRules).toHaveLength(3); - // FIXME: explicit assert condition - expect(res.remediations).toMatchSnapshot(); + expect(res.remediations).toMatchSnapshot({ + 'backend/package-lock.json': [ + { + currentVersion: '1.8.2', + datasource: 'npm', + depName: 'electron', + newVersion: '1.8.3', + }, + ], + }); }); }); }); diff --git a/lib/workers/repository/init/vulnerability.ts b/lib/workers/repository/init/vulnerability.ts index 85cbb3744d72ae..321ddf5bbb5cc9 100644 --- a/lib/workers/repository/init/vulnerability.ts +++ b/lib/workers/repository/init/vulnerability.ts @@ -4,7 +4,7 @@ import * as datasourceMaven from '../../../datasource/maven'; import { id as npmId } from '../../../datasource/npm'; import * as datasourceNuget from '../../../datasource/nuget'; import { PypiDatasource } from '../../../datasource/pypi'; -import * as datasourceRubygems from '../../../datasource/rubygems'; +import { RubyGemsDatasource } from '../../../datasource/rubygems'; import { logger } from '../../../logger'; import { platform } from '../../../platform'; import { SecurityAdvisory } from '../../../types'; @@ -73,25 +73,25 @@ export async function detectVulnerabilityAlerts( (alert.vulnerableRequirements === '= 5.0.0-security.0' || alert.vulnerableRequirements === '= 5.0.1') ) { - continue; // eslint-disable-line no-continue + continue; } try { if (alert.dismissReason) { - continue; // eslint-disable-line no-continue + continue; } if (!alert.securityVulnerability.firstPatchedVersion) { logger.debug( { alert }, 'Vulnerability alert has no firstPatchedVersion - skipping' ); - continue; // eslint-disable-line no-continue + continue; } const datasourceMapping: Record = { MAVEN: datasourceMaven.id, NPM: npmId, NUGET: datasourceNuget.id, PIP: PypiDatasource.id, - RUBYGEMS: datasourceRubygems.id, + RUBYGEMS: RubyGemsDatasource.id, }; const datasource = datasourceMapping[alert.securityVulnerability.package.ecosystem]; @@ -174,7 +174,7 @@ export async function detectVulnerabilityAlerts( } content += heading; content += '\n\n'; - // eslint-disable-next-line no-loop-func + content += sanitizeMarkdown(advisory.description); return content; }) diff --git a/lib/workers/repository/model/commit-message.ts b/lib/workers/repository/model/commit-message.ts index c62dd048b7d98b..bab8afd4f6f429 100644 --- a/lib/workers/repository/model/commit-message.ts +++ b/lib/workers/repository/model/commit-message.ts @@ -1,9 +1,9 @@ export class CommitMessage { public static readonly SEPARATOR: string = ':'; - private message: string; + private message = ''; - private prefix: string; + private prefix = ''; constructor(message = '') { this.setMessage(message); @@ -26,11 +26,11 @@ export class CommitMessage { } public setCustomPrefix(prefix?: string): void { - this.prefix = (prefix || '').trim(); + this.prefix = (prefix ?? '').trim(); } public setSemanticPrefix(type?: string, scope?: string): void { - this.prefix = (type || '').trim(); + this.prefix = (type ?? '').trim(); if (scope?.trim()) { this.prefix += `(${scope.trim()})`; diff --git a/lib/workers/repository/onboarding/branch/__snapshots__/index.spec.ts.snap b/lib/workers/repository/onboarding/branch/__snapshots__/index.spec.ts.snap deleted file mode 100644 index b94efadcf3a66c..00000000000000 --- a/lib/workers/repository/onboarding/branch/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/onboarding/branch/index checkOnboardingBranch has default onboarding config 1`] = ` -"{ - \\"$schema\\": \\"https://docs.renovatebot.com/renovate-schema.json\\" -} -" -`; - -exports[`workers/repository/onboarding/branch/index checkOnboardingBranch uses discovered onboarding config 1`] = ` -"{ - \\"$schema\\": \\"https://docs.renovatebot.com/renovate-schema.json\\", - \\"extends: [\\"some/renovate-config\\"] -} -" -`; diff --git a/lib/workers/repository/onboarding/branch/check.ts b/lib/workers/repository/onboarding/branch/check.ts index c48f445c349291..c609e41b4d7c3a 100644 --- a/lib/workers/repository/onboarding/branch/check.ts +++ b/lib/workers/repository/onboarding/branch/check.ts @@ -121,5 +121,4 @@ export const isOnboarded = async (config: RenovateConfig): Promise => { export const onboardingPrExists = async ( config: RenovateConfig -): Promise => - (await platform.getBranchPr(config.onboardingBranch)) != null; +): Promise => !!(await platform.getBranchPr(config.onboardingBranch)); diff --git a/lib/workers/repository/onboarding/branch/config.spec.ts b/lib/workers/repository/onboarding/branch/config.spec.ts index 7c9bc2e3157c80..c5e62f9c56c86c 100644 --- a/lib/workers/repository/onboarding/branch/config.spec.ts +++ b/lib/workers/repository/onboarding/branch/config.spec.ts @@ -1,5 +1,5 @@ import { RenovateConfig, getConfig } from '../../../../../test/util'; -import { setGlobalConfig } from '../../../../config/global'; +import { GlobalConfig } from '../../../../config/global'; import * as presets from '../../../../config/presets/local'; import { PRESET_DEP_NOT_FOUND } from '../../../../config/presets/util'; import { getOnboardingConfig, getOnboardingConfigContents } from './config'; @@ -12,7 +12,7 @@ describe('workers/repository/onboarding/branch/config', () => { let config: RenovateConfig; beforeAll(() => { - setGlobalConfig({ + GlobalConfig.set({ localDir: '', }); }); diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts index a449e4d080d563..5b9bf89d242e23 100644 --- a/lib/workers/repository/onboarding/branch/create.ts +++ b/lib/workers/repository/onboarding/branch/create.ts @@ -1,5 +1,5 @@ import { configFileNames } from '../../../../config/app-strings'; -import { getGlobalConfig } from '../../../../config/global'; +import { GlobalConfig } from '../../../../config/global'; import type { RenovateConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import { commitFiles } from '../../../../util/git'; @@ -26,7 +26,7 @@ export async function createOnboardingBranch( const commitMessage = commitMessageFactory.create(); // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would commit files to onboarding branch'); return null; } diff --git a/lib/workers/repository/onboarding/branch/index.spec.ts b/lib/workers/repository/onboarding/branch/index.spec.ts index 08e7117d008bcb..233f9f69848662 100644 --- a/lib/workers/repository/onboarding/branch/index.spec.ts +++ b/lib/workers/repository/onboarding/branch/index.spec.ts @@ -71,10 +71,12 @@ describe('workers/repository/onboarding/branch/index', () => { git.getFileList.mockResolvedValue(['package.json']); fs.readLocalFile.mockResolvedValue('{}'); await checkOnboardingBranch(config); - // FIXME: explicit assert condition - expect( - git.commitFiles.mock.calls[0][0].files[0].contents - ).toMatchSnapshot(); + const contents = + git.commitFiles.mock.calls[0][0].files[0].contents?.toString(); + expect(contents).toBeJsonString(); + expect(JSON.parse(contents)).toEqual({ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + }); }); it('uses discovered onboarding config', async () => { configModule.getOnboardingConfig.mockResolvedValue({ @@ -83,7 +85,7 @@ describe('workers/repository/onboarding/branch/index', () => { configModule.getOnboardingConfigContents.mockResolvedValue( '{\n' + ' "$schema": "https://docs.renovatebot.com/renovate-schema.json",\n' + - ' "extends: ["some/renovate-config"]\n' + + ' "extends": ["some/renovate-config"]\n' + '}\n' ); git.getFileList.mockResolvedValue(['package.json']); @@ -98,10 +100,13 @@ describe('workers/repository/onboarding/branch/index', () => { }, configFileNames[0] ); - // FIXME: explicit assert condition - expect( - git.commitFiles.mock.calls[0][0].files[0].contents - ).toMatchSnapshot(); + const contents = + git.commitFiles.mock.calls[0][0].files[0].contents?.toString(); + expect(contents).toBeJsonString(); + expect(JSON.parse(contents)).toEqual({ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: ['some/renovate-config'], + }); }); it('handles skipped onboarding combined with requireConfig = false', async () => { config.requireConfig = false; diff --git a/lib/workers/repository/onboarding/branch/index.ts b/lib/workers/repository/onboarding/branch/index.ts index 9910884ef0f026..26a453c781c996 100644 --- a/lib/workers/repository/onboarding/branch/index.ts +++ b/lib/workers/repository/onboarding/branch/index.ts @@ -1,5 +1,5 @@ import { mergeChildConfig } from '../../../../config'; -import { getGlobalConfig } from '../../../../config/global'; +import { GlobalConfig } from '../../../../config/global'; import type { RenovateConfig } from '../../../../config/types'; import { REPOSITORY_FORKED, @@ -70,7 +70,7 @@ export async function checkOnboardingBranch( ); } } - if (!getGlobalConfig().dryRun) { + if (!GlobalConfig.get('dryRun')) { await checkoutBranch(onboardingBranch); } const branchList = [onboardingBranch]; diff --git a/lib/workers/repository/onboarding/branch/rebase.spec.ts b/lib/workers/repository/onboarding/branch/rebase.spec.ts index c0d6e04898d750..23857990739529 100644 --- a/lib/workers/repository/onboarding/branch/rebase.spec.ts +++ b/lib/workers/repository/onboarding/branch/rebase.spec.ts @@ -1,12 +1,12 @@ import { RenovateConfig, defaultConfig, git } from '../../../../../test/util'; -import { setGlobalConfig } from '../../../../config/global'; +import { GlobalConfig } from '../../../../config/global'; import { rebaseOnboardingBranch } from './rebase'; jest.mock('../../../../util/git'); describe('workers/repository/onboarding/branch/rebase', () => { beforeAll(() => { - setGlobalConfig({ + GlobalConfig.set({ localDir: '', }); }); diff --git a/lib/workers/repository/onboarding/branch/rebase.ts b/lib/workers/repository/onboarding/branch/rebase.ts index b905ebd9848c3a..a49c4eafd4446d 100644 --- a/lib/workers/repository/onboarding/branch/rebase.ts +++ b/lib/workers/repository/onboarding/branch/rebase.ts @@ -1,5 +1,5 @@ import { configFileNames } from '../../../../config/app-strings'; -import { getGlobalConfig } from '../../../../config/global'; +import { GlobalConfig } from '../../../../config/global'; import type { RenovateConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import { @@ -43,7 +43,7 @@ export async function rebaseOnboardingBranch( const commitMessage = commitMessageFactory.create(); // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would rebase files in onboarding branch'); return null; } diff --git a/lib/workers/repository/onboarding/pr/__snapshots__/base-branch.spec.ts.snap b/lib/workers/repository/onboarding/pr/__snapshots__/base-branch.spec.ts.snap deleted file mode 100644 index c403cbb8b89076..00000000000000 --- a/lib/workers/repository/onboarding/pr/__snapshots__/base-branch.spec.ts.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/onboarding/pr/base-branch getBaseBranchDesc() describes baseBranch 1`] = ` -"You have configured Renovate to use branch \`some-branch\` as base branch. - -" -`; - -exports[`workers/repository/onboarding/pr/base-branch getBaseBranchDesc() describes baseBranches 1`] = `"You have configured Renovate to use the following baseBranches: \`some-branch\`, \`some-other-branch\`."`; diff --git a/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap b/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap index 6580a0b369f5a3..d8eaf6ade3ebbd 100644 --- a/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap +++ b/lib/workers/repository/onboarding/pr/__snapshots__/config-description.spec.ts.snap @@ -1,20 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`workers/repository/onboarding/pr/config-description getConfigDesc() assignees, labels and schedule 1`] = ` -" -### Configuration Summary - -Based on the default config's presets, Renovate will: - - - Start dependency updates only once this onboarding PR is merged - - Run Renovate on following schedule: before 5am - -🔡 Would you like to change the way Renovate is upgrading your dependencies? Simply edit the \`renovate.json\` in this branch with your custom config and the list of Pull Requests in the \\"What to Expect\\" section below will be updated the next time Renovate runs. - ---- -" -`; - exports[`workers/repository/onboarding/pr/config-description getConfigDesc() contains the onboardingConfigFileName if set 1`] = ` " ### Configuration Summary @@ -77,5 +62,3 @@ Based on the default config's presets, Renovate will: --- " `; - -exports[`workers/repository/onboarding/pr/config-description getConfigDesc() returns empty 1`] = `""`; diff --git a/lib/workers/repository/onboarding/pr/__snapshots__/errors-warnings.spec.ts.snap b/lib/workers/repository/onboarding/pr/__snapshots__/errors-warnings.spec.ts.snap deleted file mode 100644 index 0262a1986aaba4..00000000000000 --- a/lib/workers/repository/onboarding/pr/__snapshots__/errors-warnings.spec.ts.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/onboarding/pr/errors-warnings getDepWarnings() returns warning text 1`] = ` -" ---- - -### ⚠ Dependency Lookup Warnings ⚠ - -Please correct - or verify that you can safely ignore - these lookup failures before you merge this PR. - -- \`Warning 1\` -- \`Warning 2\` - -Files affected: \`package.json\`, \`backend/package.json\`, \`Dockerfile\` - -" -`; - -exports[`workers/repository/onboarding/pr/errors-warnings getErrors() returns error text 1`] = ` -" -# Errors (1) - -Renovate has found errors that you should fix (in this branch) before finishing this PR. - -- \`renovate.json\`: Failed to parse - ---- -" -`; - -exports[`workers/repository/onboarding/pr/errors-warnings getWarnings() returns warning text 1`] = ` -" -# Warnings (1) - -Please correct - or verify that you can safely ignore - these warnings before you merge this PR. - -- \`foo\`: Failed to look up dependency - ---- -" -`; diff --git a/lib/workers/repository/onboarding/pr/__snapshots__/pr-list.spec.ts.snap b/lib/workers/repository/onboarding/pr/__snapshots__/pr-list.spec.ts.snap deleted file mode 100644 index bc5336ad222400..00000000000000 --- a/lib/workers/repository/onboarding/pr/__snapshots__/pr-list.spec.ts.snap +++ /dev/null @@ -1,60 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/onboarding/pr/pr-list getPrList() handles empty 1`] = ` -" -### What to Expect - -It looks like your repository dependencies are already up-to-date and no Pull Requests will be necessary right away. -" -`; - -exports[`workers/repository/onboarding/pr/pr-list getPrList() handles multiple 1`] = ` -" -### What to Expect - -With your current configuration, Renovate will create 2 Pull Requests: - -
-Pin dependencies - - - Branch name: \`renovate/pin-dependencies\` - - Merge into: \`some-other\` - - Pin [a](https://a) to \`1.1.0\` - - Pin b to \`1.5.3\` - - -
- -
-Update a to v2 - - - Branch name: \`renovate/a-2.x\` - - Upgrade [a](https://a) to \`undefined\` - - -
- -
- -🚸 Branch creation will be limited to maximum 1 per hour, so it doesn't swamp any CI resources or spam the project. See docs for \`prhourlylimit\` for details. - -" -`; - -exports[`workers/repository/onboarding/pr/pr-list getPrList() has special lock file maintenance description 1`] = ` -" -### What to Expect - -With your current configuration, Renovate will create 1 Pull Request: - -
-Lock file maintenance - - - Schedule: [\\"before 5am\\"] - - Branch name: \`renovate/lock-file-maintenance\` - - Regenerate lock files to use latest dependency versions - -
- -" -`; diff --git a/lib/workers/repository/onboarding/pr/base-branch.spec.ts b/lib/workers/repository/onboarding/pr/base-branch.spec.ts index 220da0b7c7731f..39bcac7a8b35ea 100644 --- a/lib/workers/repository/onboarding/pr/base-branch.spec.ts +++ b/lib/workers/repository/onboarding/pr/base-branch.spec.ts @@ -16,14 +16,16 @@ describe('workers/repository/onboarding/pr/base-branch', () => { it('describes baseBranch', () => { config.baseBranches = ['some-branch']; const res = getBaseBranchDesc(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res.trim()).toBe( + 'You have configured Renovate to use branch `some-branch` as base branch.' + ); }); it('describes baseBranches', () => { config.baseBranches = ['some-branch', 'some-other-branch']; const res = getBaseBranchDesc(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res.trim()).toBe( + 'You have configured Renovate to use the following baseBranches: `some-branch`, `some-other-branch`.' + ); }); }); }); diff --git a/lib/workers/repository/onboarding/pr/config-description.spec.ts b/lib/workers/repository/onboarding/pr/config-description.spec.ts index 2229c6c4cc2ff8..7bfeb3403d4cd1 100644 --- a/lib/workers/repository/onboarding/pr/config-description.spec.ts +++ b/lib/workers/repository/onboarding/pr/config-description.spec.ts @@ -12,8 +12,7 @@ describe('workers/repository/onboarding/pr/config-description', () => { it('returns empty', () => { delete config.description; const res = getConfigDesc(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toBe(''); }); it('returns a full list', () => { const packageFiles: Record = { @@ -37,8 +36,20 @@ describe('workers/repository/onboarding/pr/config-description', () => { config.labels = ['renovate', 'deps']; config.schedule = ['before 5am']; const res = getConfigDesc(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchInlineSnapshot(` + " + ### Configuration Summary + + Based on the default config's presets, Renovate will: + + - Start dependency updates only once this onboarding PR is merged + - Run Renovate on following schedule: before 5am + + 🔡 Would you like to change the way Renovate is upgrading your dependencies? Simply edit the \`renovate.json\` in this branch with your custom config and the list of Pull Requests in the \\"What to Expect\\" section below will be updated the next time Renovate runs. + + --- + " + `); }); it('contains the onboardingConfigFileName if set', () => { delete config.description; diff --git a/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts b/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts index 7790cbd30704dd..4d04bfc461cdee 100644 --- a/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts +++ b/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts @@ -17,8 +17,17 @@ describe('workers/repository/onboarding/pr/errors-warnings', () => { }, ]; const res = getWarnings(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchInlineSnapshot(` + " + # Warnings (1) + + Please correct - or verify that you can safely ignore - these warnings before you merge this PR. + + - \`foo\`: Failed to look up dependency + + --- + " + `); }); }); describe('getDepWarnings()', () => { @@ -58,8 +67,21 @@ describe('workers/repository/onboarding/pr/errors-warnings', () => { ], }; const res = getDepWarnings(packageFiles); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchInlineSnapshot(` + " + --- + + ### ⚠ Dependency Lookup Warnings ⚠ + + Please correct - or verify that you can safely ignore - these lookup failures before you merge this PR. + + - \`Warning 1\` + - \`Warning 2\` + + Files affected: \`package.json\`, \`backend/package.json\`, \`Dockerfile\` + + " + `); }); }); describe('getErrors()', () => { @@ -76,8 +98,17 @@ describe('workers/repository/onboarding/pr/errors-warnings', () => { }, ]; const res = getErrors(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchInlineSnapshot(` + " + # Errors (1) + + Renovate has found errors that you should fix (in this branch) before finishing this PR. + + - \`renovate.json\`: Failed to parse + + --- + " + `); }); }); }); diff --git a/lib/workers/repository/onboarding/pr/index.spec.ts b/lib/workers/repository/onboarding/pr/index.spec.ts index caa5e23b1f4806..debdb38980893b 100644 --- a/lib/workers/repository/onboarding/pr/index.spec.ts +++ b/lib/workers/repository/onboarding/pr/index.spec.ts @@ -5,7 +5,7 @@ import { partial, platform, } from '../../../../../test/util'; -import { setGlobalConfig } from '../../../../config/global'; +import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; import type { PackageFile } from '../../../../manager/types'; import { Pr } from '../../../../platform'; @@ -31,7 +31,7 @@ describe('workers/repository/onboarding/pr/index', () => { branches = []; platform.massageMarkdown = jest.fn((input) => input); platform.createPr.mockResolvedValueOnce(partial({})); - setGlobalConfig(); + GlobalConfig.reset(); }); let createPrBody: string; it('returns if onboarded', async () => { @@ -89,7 +89,7 @@ describe('workers/repository/onboarding/pr/index', () => { expect(platform.createPr).toHaveBeenCalledTimes(1); }); it('dryrun of updates PR when modified', async () => { - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); config.baseBranch = 'some-branch'; platform.getBranchPr.mockResolvedValueOnce( partial({ @@ -108,7 +108,7 @@ describe('workers/repository/onboarding/pr/index', () => { ); }); it('dryrun of creates PR', async () => { - setGlobalConfig({ dryRun: true }); + GlobalConfig.set({ dryRun: true }); await ensureOnboardingPr(config, packageFiles, branches); expect(logger.info).toHaveBeenCalledWith( 'DRY-RUN: Would check branch renovate/configure' diff --git a/lib/workers/repository/onboarding/pr/index.ts b/lib/workers/repository/onboarding/pr/index.ts index 0f5ac5dcc48972..bf0c7be77dd29f 100644 --- a/lib/workers/repository/onboarding/pr/index.ts +++ b/lib/workers/repository/onboarding/pr/index.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../../../config/global'; +import { GlobalConfig } from '../../../../config/global'; import type { RenovateConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import type { PackageFile } from '../../../../manager/types'; @@ -66,7 +66,7 @@ If you need any further assistance then you can also [request help here](${confi prBody = prBody.replace('{{PACKAGE FILES}}\n', ''); } let configDesc = ''; - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info(`DRY-RUN: Would check branch ${config.onboardingBranch}`); } else if (await isBranchModified(config.onboardingBranch)) { configDesc = emojify( @@ -114,7 +114,7 @@ If you need any further assistance then you can also [request help here](${confi return; } // PR must need updating - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would update onboarding PR'); } else { await platform.updatePr({ @@ -129,7 +129,7 @@ If you need any further assistance then you can also [request help here](${confi logger.debug('Creating onboarding PR'); const labels: string[] = config.addLabels ?? []; try { - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Would create onboarding PR'); } else { const pr = await platform.createPr({ diff --git a/lib/workers/repository/onboarding/pr/pr-list.spec.ts b/lib/workers/repository/onboarding/pr/pr-list.spec.ts index 7971514845a2ce..c0bef2c4d82603 100644 --- a/lib/workers/repository/onboarding/pr/pr-list.spec.ts +++ b/lib/workers/repository/onboarding/pr/pr-list.spec.ts @@ -12,8 +12,13 @@ describe('workers/repository/onboarding/pr/pr-list', () => { it('handles empty', () => { const branches: BranchConfig[] = []; const res = getPrList(config, branches); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchInlineSnapshot(` + " + ### What to Expect + + It looks like your repository dependencies are already up-to-date and no Pull Requests will be necessary right away. + " + `); }); it('has special lock file maintenance description', () => { const branches = [ @@ -29,8 +34,23 @@ describe('workers/repository/onboarding/pr/pr-list', () => { }, ]; const res = getPrList(config, branches); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchInlineSnapshot(` + " + ### What to Expect + + With your current configuration, Renovate will create 1 Pull Request: + +
+ Lock file maintenance + + - Schedule: [\\"before 5am\\"] + - Branch name: \`renovate/lock-file-maintenance\` + - Regenerate lock files to use latest dependency versions + +
+ + " + `); }); it('handles multiple', () => { const branches = [ @@ -70,8 +90,38 @@ describe('workers/repository/onboarding/pr/pr-list', () => { ]; config.prHourlyLimit = 1; const res = getPrList(config, branches); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchInlineSnapshot(` + " + ### What to Expect + + With your current configuration, Renovate will create 2 Pull Requests: + +
+ Pin dependencies + + - Branch name: \`renovate/pin-dependencies\` + - Merge into: \`some-other\` + - Pin [a](https://a) to \`1.1.0\` + - Pin b to \`1.5.3\` + + +
+ +
+ Update a to v2 + + - Branch name: \`renovate/a-2.x\` + - Upgrade [a](https://a) to \`undefined\` + + +
+ +
+ + 🚸 Branch creation will be limited to maximum 1 per hour, so it doesn't swamp any CI resources or spam the project. See docs for \`prhourlylimit\` for details. + + " + `); }); }); }); diff --git a/lib/workers/repository/process/__snapshots__/deprecated.spec.ts.snap b/lib/workers/repository/process/__snapshots__/deprecated.spec.ts.snap deleted file mode 100644 index e30d27e02a7f8f..00000000000000 --- a/lib/workers/repository/process/__snapshots__/deprecated.spec.ts.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/process/deprecated raiseDeprecationWarnings() raises deprecation warnings 1`] = ` -Array [ - Array [ - Object { - "body": "foo is deprecated - -Affected package file(s): \`package.json\`, \`frontend/package.json\` - -If you don't care about this, you can close this issue and not be warned about \`foo\`'s deprecation again. If you would like to completely disable all future deprecation warnings then add the following to your config: - -\`\`\` -\\"suppressNotifications\\": [\\"deprecationWarningIssues\\"] -\`\`\` - -", - "once": true, - "title": "Dependency deprecation warning: foo (npm)", - }, - ], -] -`; diff --git a/lib/workers/repository/process/__snapshots__/extract-update.spec.ts.snap b/lib/workers/repository/process/__snapshots__/extract-update.spec.ts.snap deleted file mode 100644 index 2b515357e01873..00000000000000 --- a/lib/workers/repository/process/__snapshots__/extract-update.spec.ts.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`workers/repository/process/extract-update extract() runs with baseBranches 1`] = `undefined`; - -exports[`workers/repository/process/extract-update extract() runs with no baseBranches 1`] = ` -Object { - "branchList": Array [ - "branchName", - ], - "branches": Array [ - Object { - "branchName": "some-branch", - "upgrades": Array [], - }, - ], - "packageFiles": undefined, -} -`; diff --git a/lib/workers/repository/process/deprecated.spec.ts b/lib/workers/repository/process/deprecated.spec.ts index dd53179b765795..c9ca70c71537f3 100644 --- a/lib/workers/repository/process/deprecated.spec.ts +++ b/lib/workers/repository/process/deprecated.spec.ts @@ -59,8 +59,9 @@ describe('workers/repository/process/deprecated', () => { ]; platform.getIssueList.mockResolvedValue(mockIssue); await raiseDeprecationWarnings(config, packageFiles); - // FIXME: explicit assert condition - expect(platform.ensureIssue.mock.calls).toMatchSnapshot(); + expect(platform.ensureIssue.mock.calls).toMatchObject([ + [{ once: true, title: 'Dependency deprecation warning: foo (npm)' }], + ]); expect(platform.getIssueList).toHaveBeenCalledTimes(1); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); }); diff --git a/lib/workers/repository/process/deprecated.ts b/lib/workers/repository/process/deprecated.ts index 2c438d83948096..af36e8decab945 100644 --- a/lib/workers/repository/process/deprecated.ts +++ b/lib/workers/repository/process/deprecated.ts @@ -1,4 +1,4 @@ -import { getGlobalConfig } from '../../../config/global'; +import { GlobalConfig } from '../../../config/global'; import type { RenovateConfig } from '../../../config/types'; import { logger } from '../../../logger'; import type { PackageFile } from '../../../manager/types'; @@ -55,7 +55,7 @@ export async function raiseDeprecationWarnings( .join(', ')}`; issueBody += `\n\nIf you don't care about this, you can close this issue and not be warned about \`${depName}\`'s deprecation again. If you would like to completely disable all future deprecation warnings then add the following to your config:\n\n\`\`\`\n"suppressNotifications": ["deprecationWarningIssues"]\n\`\`\`\n\n`; // istanbul ignore if - if (getGlobalConfig().dryRun) { + if (GlobalConfig.get('dryRun')) { logger.info('DRY-RUN: Ensure deprecation warning issue for ' + depName); } else { const ensureOnce = true; @@ -63,6 +63,7 @@ export async function raiseDeprecationWarnings( title: issueTitle, body: issueBody, once: ensureOnce, + confidential: config.confidential, }); } } diff --git a/lib/workers/repository/process/extract-update.spec.ts b/lib/workers/repository/process/extract-update.spec.ts index daa9840aadff9a..bd50bd1f76d168 100644 --- a/lib/workers/repository/process/extract-update.spec.ts +++ b/lib/workers/repository/process/extract-update.spec.ts @@ -32,8 +32,16 @@ describe('workers/repository/process/extract-update', () => { git.checkoutBranch.mockResolvedValueOnce('123test'); const packageFiles = await extract(config); const res = await lookup(config, packageFiles); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toEqual({ + branchList: ['branchName'], + branches: [ + { + branchName: 'some-branch', + upgrades: [], + }, + ], + packageFiles: undefined, + }); await expect(update(config, res.branches)).resolves.not.toThrow(); }); it('runs with baseBranches', async () => { @@ -45,8 +53,7 @@ describe('workers/repository/process/extract-update', () => { git.checkoutBranch.mockResolvedValueOnce('123test'); repositoryCache.getCache.mockReturnValueOnce({ scan: {} }); const packageFiles = await extract(config); - // FIXME: explicit assert condition - expect(packageFiles).toMatchSnapshot(); + expect(packageFiles).toBeUndefined(); }); it('uses repository cache', async () => { const packageFiles: Record = {}; diff --git a/lib/workers/repository/process/fetch.spec.ts b/lib/workers/repository/process/fetch.spec.ts index 25b9e901a49f20..bf44f283a16a4d 100644 --- a/lib/workers/repository/process/fetch.spec.ts +++ b/lib/workers/repository/process/fetch.spec.ts @@ -45,9 +45,9 @@ describe('workers/repository/process/fetch', () => { }; await fetchUpdates(config, packageFiles); expect(packageFiles).toMatchSnapshot(); - expect(packageFiles.npm[0].deps[0].skipReason).toEqual('ignored'); + expect(packageFiles.npm[0].deps[0].skipReason).toBe('ignored'); expect(packageFiles.npm[0].deps[0].updates).toHaveLength(0); - expect(packageFiles.npm[0].deps[1].skipReason).toEqual('disabled'); + expect(packageFiles.npm[0].deps[1].skipReason).toBe('disabled'); expect(packageFiles.npm[0].deps[1].updates).toHaveLength(0); }); it('fetches updates', async () => { diff --git a/lib/workers/repository/process/fetch.ts b/lib/workers/repository/process/fetch.ts index e4d85cb67c071d..6cd7cd90943e06 100644 --- a/lib/workers/repository/process/fetch.ts +++ b/lib/workers/repository/process/fetch.ts @@ -59,7 +59,7 @@ async function fetchManagerPackagerFileUpdates( { manager, packageFile, queueLength: queue.length }, 'fetchManagerPackagerFileUpdates starting with concurrency' ); - // eslint-disable-next-line no-param-reassign + pFile.deps = await pAll(queue, { concurrency: 5 }); logger.trace({ packageFile }, 'fetchManagerPackagerFileUpdates finished'); } diff --git a/lib/workers/repository/process/limits.spec.ts b/lib/workers/repository/process/limits.spec.ts index 7f1128ea90ceee..eb16171a052c7c 100644 --- a/lib/workers/repository/process/limits.spec.ts +++ b/lib/workers/repository/process/limits.spec.ts @@ -39,17 +39,17 @@ describe('workers/repository/process/limits', () => { branchPrefix: 'foo/', onboardingBranch: 'bar/configure', }); - expect(res).toEqual(7); + expect(res).toBe(7); }); it('returns prHourlyLimit if errored', async () => { config.prHourlyLimit = 2; platform.getPrList.mockRejectedValue('Unknown error'); const res = await limits.getPrHourlyRemaining(config); - expect(res).toEqual(2); + expect(res).toBe(2); }); it('returns 99 if no hourly limit', async () => { const res = await limits.getPrHourlyRemaining(config); - expect(res).toEqual(99); + expect(res).toBe(99); }); }); describe('getConcurrentPrsRemaining()', () => { @@ -68,11 +68,11 @@ describe('workers/repository/process/limits', () => { { branchName: null }, ] as never; const res = await limits.getConcurrentPrsRemaining(config, branches); - expect(res).toEqual(19); + expect(res).toBe(19); }); it('returns 99 if no concurrent limit', async () => { const res = await limits.getConcurrentPrsRemaining(config, []); - expect(res).toEqual(99); + expect(res).toBe(99); }); }); @@ -81,12 +81,12 @@ describe('workers/repository/process/limits', () => { config.prHourlyLimit = 5; platform.getPrList.mockResolvedValueOnce([]); const res = await limits.getPrsRemaining(config, []); - expect(res).toEqual(5); + expect(res).toBe(5); }); it('returns concurrent limit', async () => { config.prConcurrentLimit = 5; const res = await limits.getPrsRemaining(config, []); - expect(res).toEqual(5); + expect(res).toBe(5); }); }); @@ -97,7 +97,7 @@ describe('workers/repository/process/limits', () => { const res = limits.getConcurrentBranchesRemaining(config, [ { branchName: 'foo' }, ] as never); - expect(res).toEqual(19); + expect(res).toBe(19); }); it('defaults to prConcurrentLimit', () => { config.branchConcurrentLimit = null; @@ -106,22 +106,22 @@ describe('workers/repository/process/limits', () => { const res = limits.getConcurrentBranchesRemaining(config, [ { branchName: 'foo' }, ] as never); - expect(res).toEqual(19); + expect(res).toBe(19); }); it('does not use prConcurrentLimit for explicit branchConcurrentLimit=0', () => { config.branchConcurrentLimit = 0; config.prConcurrentLimit = 20; const res = limits.getConcurrentBranchesRemaining(config, []); - expect(res).toEqual(99); + expect(res).toBe(99); }); it('returns 99 if no limits are set', () => { const res = limits.getConcurrentBranchesRemaining(config, []); - expect(res).toEqual(99); + expect(res).toBe(99); }); it('returns prConcurrentLimit if errored', () => { config.branchConcurrentLimit = 2; const res = limits.getConcurrentBranchesRemaining(config, null); - expect(res).toEqual(2); + expect(res).toBe(2); }); }); @@ -138,7 +138,7 @@ describe('workers/repository/process/limits', () => { }, [] ) - ).resolves.toEqual(3); + ).resolves.toBe(3); await expect( limits.getBranchesRemaining( @@ -149,7 +149,7 @@ describe('workers/repository/process/limits', () => { }, [] ) - ).resolves.toEqual(7); + ).resolves.toBe(7); }); }); }); diff --git a/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap b/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap index 85185e81575f8c..dd710b7fc1f273 100644 --- a/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap +++ b/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap @@ -195,6 +195,26 @@ exports[`workers/repository/process/lookup/index .lookupUpdates() handles packag exports[`workers/repository/process/lookup/index .lookupUpdates() handles pypi 404 1`] = `Array []`; +exports[`workers/repository/process/lookup/index .lookupUpdates() handles replacements 1`] = ` +Object { + "changelogUrl": undefined, + "currentVersion": "1.4.1", + "dependencyUrl": undefined, + "fixedVersion": "1.4.1", + "homepage": undefined, + "sourceUrl": "https://github.com/kriskowal/q", + "updates": Array [ + Object { + "newName": "r", + "newValue": "2.0.0", + "updateType": "replacement", + }, + ], + "versioning": "npm", + "warnings": Array [], +} +`; + exports[`workers/repository/process/lookup/index .lookupUpdates() handles sourceUrl packageRules with version restrictions 1`] = ` Object { "changelogUrl": undefined, diff --git a/lib/workers/repository/process/lookup/bucket.ts b/lib/workers/repository/process/lookup/bucket.ts index daa6c5f3842d98..112815aa962509 100644 --- a/lib/workers/repository/process/lookup/bucket.ts +++ b/lib/workers/repository/process/lookup/bucket.ts @@ -19,6 +19,10 @@ export function getBucket( } const fromMajor = versioning.getMajor(currentVersion); const toMajor = versioning.getMajor(newVersion); + // istanbul ignore if + if (toMajor === null) { + return null; + } if (fromMajor !== toMajor) { if (separateMultipleMajor) { return `major-${toMajor}`; diff --git a/lib/workers/repository/process/lookup/current.ts b/lib/workers/repository/process/lookup/current.ts index 94f8d12fd4e7d9..0b7113f703988e 100644 --- a/lib/workers/repository/process/lookup/current.ts +++ b/lib/workers/repository/process/lookup/current.ts @@ -2,16 +2,15 @@ import is from '@sindresorhus/is'; import { logger } from '../../../../logger'; import { regEx } from '../../../../util/regex'; import type { VersioningApi } from '../../../../versioning/types'; -import type { LookupUpdateConfig } from './types'; export function getCurrentVersion( - config: LookupUpdateConfig, + currentValue: string, + lockedVersion: string, versioning: VersioningApi, rangeStrategy: string, latestVersion: string, allVersions: string[] ): string | null { - const { currentValue, lockedVersion } = config; // istanbul ignore if if (!is.string(currentValue)) { return null; diff --git a/lib/workers/repository/process/lookup/filter-checks.spec.ts b/lib/workers/repository/process/lookup/filter-checks.spec.ts index d9898d6204b79e..092e145b22d6a3 100644 --- a/lib/workers/repository/process/lookup/filter-checks.spec.ts +++ b/lib/workers/repository/process/lookup/filter-checks.spec.ts @@ -60,7 +60,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeFalse(); expect(res.pendingReleases).toHaveLength(0); - expect(res.release.version).toEqual('1.0.4'); + expect(res.release.version).toBe('1.0.4'); }); it('returns non-pending latest release if internalChecksFilter=flexible and none pass checks', async () => { @@ -75,7 +75,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeFalse(); expect(res.pendingReleases).toHaveLength(0); - expect(res.release.version).toEqual('1.0.4'); + expect(res.release.version).toBe('1.0.4'); }); it('returns pending latest release if internalChecksFilter=strict and none pass checks', async () => { @@ -90,7 +90,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeTrue(); expect(res.pendingReleases).toHaveLength(0); - expect(res.release.version).toEqual('1.0.4'); + expect(res.release.version).toBe('1.0.4'); }); it('returns non-latest release if internalChecksFilter=strict and some pass checks', async () => { @@ -105,7 +105,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeFalse(); expect(res.pendingReleases).toHaveLength(2); - expect(res.release.version).toEqual('1.0.2'); + expect(res.release.version).toBe('1.0.2'); }); it('returns non-latest release if internalChecksFilter=flexible and some pass checks', async () => { @@ -120,7 +120,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeFalse(); expect(res.pendingReleases).toHaveLength(2); - expect(res.release.version).toEqual('1.0.2'); + expect(res.release.version).toBe('1.0.2'); }); it('picks up stabilityDays settings from hostRules', async () => { @@ -136,7 +136,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeFalse(); expect(res.pendingReleases).toHaveLength(0); - expect(res.release.version).toEqual('1.0.4'); + expect(res.release.version).toBe('1.0.4'); }); it('picks up stabilityDays settings from updateType', async () => { @@ -151,7 +151,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeFalse(); expect(res.pendingReleases).toHaveLength(1); - expect(res.release.version).toEqual('1.0.3'); + expect(res.release.version).toBe('1.0.3'); }); it('picks up minimumConfidence settings from updateType', async () => { @@ -171,7 +171,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res).toMatchSnapshot(); expect(res.pendingChecks).toBeFalse(); expect(res.pendingReleases).toHaveLength(3); - expect(res.release.version).toEqual('1.0.1'); + expect(res.release.version).toBe('1.0.1'); }); }); }); diff --git a/lib/workers/repository/process/lookup/filter-checks.ts b/lib/workers/repository/process/lookup/filter-checks.ts index 1208a7d716e0e9..73c0535fb5c189 100644 --- a/lib/workers/repository/process/lookup/filter-checks.ts +++ b/lib/workers/repository/process/lookup/filter-checks.ts @@ -66,7 +66,7 @@ export async function filterInternalChecks( `Release ${candidateRelease.version} is pending status checks` ); pendingReleases.unshift(candidateRelease); - continue; // eslint-disable-line no-continue + continue; } } if (isActiveConfidenceLevel(minimumConfidence)) { @@ -83,7 +83,7 @@ export async function filterInternalChecks( `Release ${candidateRelease.version} is pending status checks` ); pendingReleases.unshift(candidateRelease); - continue; // eslint-disable-line no-continue + continue; } } // If we get to here, then the release is OK and we can stop iterating diff --git a/lib/workers/repository/process/lookup/filter.ts b/lib/workers/repository/process/lookup/filter.ts index 60c290a637dfeb..522840e1bec3c1 100644 --- a/lib/workers/repository/process/lookup/filter.ts +++ b/lib/workers/repository/process/lookup/filter.ts @@ -2,7 +2,7 @@ import * as semver from 'semver'; import { CONFIG_VALIDATION } from '../../../../constants/error-messages'; import type { Release } from '../../../../datasource/types'; import { logger } from '../../../../logger'; -import { configRegexPredicate, isConfigRegex } from '../../../../util/regex'; +import { configRegexPredicate } from '../../../../util/regex'; import type { VersioningApi } from '../../../../versioning'; import * as npmVersioning from '../../../../versioning/npm'; import * as pep440 from '../../../../versioning/pep440'; @@ -29,6 +29,7 @@ export function filterVersions( } return true; } + // istanbul ignore if: shouldn't happen if (!currentVersion) { return []; } @@ -60,10 +61,10 @@ export function filterVersions( } if (allowedVersions) { - if (isConfigRegex(allowedVersions)) { - const isAllowed = configRegexPredicate(allowedVersions); + const isAllowedPred = configRegexPredicate(allowedVersions); + if (isAllowedPred) { filteredVersions = filteredVersions.filter(({ version }) => - isAllowed(version) + isAllowedPred(version) ); } else if (versioning.isValid(allowedVersions)) { filteredVersions = filteredVersions.filter((v) => diff --git a/lib/workers/repository/process/lookup/generate.ts b/lib/workers/repository/process/lookup/generate.ts index 8a5086ac1c7ae7..6a5a3b7c6b6011 100644 --- a/lib/workers/repository/process/lookup/generate.ts +++ b/lib/workers/repository/process/lookup/generate.ts @@ -32,22 +32,32 @@ export function generateUpdate( } } const { currentValue } = config; - try { - update.newValue = versioning.getNewValue({ - currentValue, - rangeStrategy, - currentVersion, - newVersion, - }); - } catch (err) /* istanbul ignore next */ { - logger.warn( - { err, currentValue, rangeStrategy, currentVersion, newVersion }, - 'getNewValue error' - ); + if (currentValue) { + try { + update.newValue = versioning.getNewValue({ + currentValue, + rangeStrategy, + currentVersion, + newVersion, + }); + } catch (err) /* istanbul ignore next */ { + logger.warn( + { err, currentValue, rangeStrategy, currentVersion, newVersion }, + 'getNewValue error' + ); + update.newValue = currentValue; + } + } else { update.newValue = currentValue; } update.newMajor = versioning.getMajor(newVersion); update.newMinor = versioning.getMinor(newVersion); + // istanbul ignore if + if (!update.updateType && !currentVersion) { + logger.debug({ update }, 'Update has no currentVersion'); + update.newValue = currentValue; + return update; + } update.updateType = update.updateType || getUpdateType(config, versioning, currentVersion, newVersion); diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index b1db1cba7cc65b..f4e748e7e110f1 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -71,8 +71,10 @@ describe('workers/repository/process/lookup/index', () => { config.datasource = datasourceNpmId; config.rollbackPrs = true; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '0.9.7', updateType: 'rollback' }, + { newValue: '1.4.1', updateType: 'major' }, + ]); }); it('returns rollback for ranged version', async () => { config.currentValue = '^0.9.99'; @@ -80,8 +82,9 @@ describe('workers/repository/process/lookup/index', () => { config.datasource = datasourceNpmId; config.rollbackPrs = true; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '^0.9.7', updateType: 'rollback' }, + ]); }); it('supports minor and major upgrades for tilde ranges', async () => { config.currentValue = '^0.4.0'; @@ -89,8 +92,11 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '0.4.4', updateType: 'pin' }, + { newValue: '0.9.7', updateType: 'minor' }, + { newValue: '1.4.1', updateType: 'major' }, + ]); }); it('supports lock file updates mixed with regular updates', async () => { config.currentValue = '^0.4.0'; @@ -100,8 +106,11 @@ describe('workers/repository/process/lookup/index', () => { config.separateMinorPatch = true; config.lockedVersion = '0.4.0'; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { isLockfileUpdate: true, newValue: '^0.4.0', updateType: 'patch' }, + { newValue: '^0.9.0', updateType: 'minor' }, + { newValue: '^1.0.0', updateType: 'major' }, + ]); }); it('returns multiple updates if grouping but separateMajorMinor=true', async () => { config.groupName = 'somegroup'; @@ -145,8 +154,11 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '0.4.4', updateType: 'pin' }, + { newValue: '0.9.7', updateType: 'minor' }, + { newValue: '1.4.1', updateType: 'major' }, + ]); }); it('enforces allowedVersions', async () => { config.currentValue = '0.4.0'; @@ -209,8 +221,8 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(2); - expect(res.updates[0].updateType).toEqual('patch'); - expect(res.updates[1].updateType).toEqual('major'); + expect(res.updates[0].updateType).toBe('patch'); + expect(res.updates[1].updateType).toBe('major'); }); it('returns minor update if automerging both patch and minor', async () => { config.patch = { @@ -226,7 +238,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); - expect(res.updates[0].updateType).toEqual('patch'); + expect(res.updates[0].updateType).toBe('patch'); }); it('returns patch update if separateMinorPatch', async () => { config.separateMinorPatch = true; @@ -235,8 +247,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '0.9.7', updateType: 'patch' }, + { newValue: '1.4.1', updateType: 'major' }, + ]); }); it('returns patch minor and major', async () => { config.separateMinorPatch = true; @@ -256,8 +270,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '0.4.4', updateType: 'pin' }, + { newValue: '1.4.1', updateType: 'major' }, + ]); }); it('disables major release separation (minor)', async () => { config.separateMajorMinor = false; @@ -266,8 +282,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.4.1', updateType: 'minor' }, + ]); }); it('uses minimum version for vulnerabilityAlerts', async () => { config.currentValue = '1.0.0'; @@ -285,8 +302,11 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '0.4.4', updateType: 'pin' }, + { newValue: '0.9.7', updateType: 'minor' }, + { newValue: '1.4.1', updateType: 'major' }, + ]); }); it('ignores pinning for ranges when other upgrade exists', async () => { config.currentValue = '~0.9.0'; @@ -294,8 +314,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '0.9.7', updateType: 'pin' }, + { newValue: '1.4.1', updateType: 'major' }, + ]); }); it('upgrades minor ranged versions', async () => { config.currentValue = '~1.0.0'; @@ -303,8 +325,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.0.1', updateType: 'pin' }, + { newValue: '1.4.1', updateType: 'minor' }, + ]); }); it('handles update-lockfile', async () => { config.currentValue = '^1.2.1'; @@ -315,7 +339,32 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); - expect(res.updates[0].updateType).toEqual('minor'); + expect(res.updates[0].updateType).toBe('minor'); + }); + it('handles unconstrainedValue values', async () => { + config.lockedVersion = '1.2.1'; + config.rangeStrategy = 'update-lockfile'; + config.depName = 'q'; + config.datasource = datasourceNpmId; + httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); + const res = await lookup.lookupUpdates(config); + expect(res.updates).toMatchInlineSnapshot(` + Array [ + Object { + "bucket": "non-major", + "isLockfileUpdate": true, + "isRange": true, + "newMajor": 1, + "newMinor": 4, + "newValue": undefined, + "newVersion": "1.4.1", + "releaseTimestamp": "2015-05-17T04:25:07.299Z", + "updateType": "minor", + }, + ] + `); + expect(res.updates[0].newValue).toBeUndefined(); + expect(res.updates[0].updateType).toBe('minor'); }); it('widens minor ranged versions if configured', async () => { config.currentValue = '~1.3.0'; @@ -323,8 +372,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '~1.3.0 || ~1.4.0', updateType: 'minor' }, + ]); }); it('replaces minor complex ranged versions if configured', async () => { config.currentValue = '~1.2.0 || ~1.3.0'; @@ -332,8 +382,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '~1.4.0', updateType: 'minor' }, + ]); }); it('widens major ranged versions if configured', async () => { config.currentValue = '^2.0.0'; @@ -344,8 +395,9 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://registry.npmjs.org') .get('/webpack') .reply(200, webpackJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '^2.0.0 || ^3.0.0', updateType: 'major' }, + ]); }); it('replaces major complex ranged versions if configured', async () => { config.currentValue = '^1.0.0 || ^2.0.0'; @@ -356,8 +408,9 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://registry.npmjs.org') .get('/webpack') .reply(200, webpackJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '^3.0.0', updateType: 'major' }, + ]); }); it('pins minor ranged versions', async () => { config.currentValue = '^1.0.0'; @@ -365,8 +418,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.4.1', updateType: 'pin' }, + ]); }); it('uses the locked version for pinning', async () => { config.currentValue = '^1.0.0'; @@ -375,8 +429,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.0.0', updateType: 'pin' }, + { newValue: '1.4.1', updateType: 'minor' }, + ]); }); it('ignores minor ranged versions when not pinning', async () => { config.rangeStrategy = 'replace'; @@ -401,8 +457,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.3.0', updateType: 'pin' }, + { newValue: '1.4.1', updateType: 'minor' }, + ]); }); it('upgrades .x minor ranges', async () => { config.currentValue = '1.3.x'; @@ -410,8 +468,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.3.0', updateType: 'pin' }, + { newValue: '1.4.1', updateType: 'minor' }, + ]); }); it('upgrades tilde ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -419,8 +479,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '~1.4.0', updateType: 'minor' }, + ]); }); it('upgrades .x major ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -428,8 +489,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.x', updateType: 'major' }, + ]); }); it('upgrades .x minor ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -437,8 +499,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.4.x', updateType: 'minor' }, + ]); }); it('upgrades .x complex minor ranges without pinning', async () => { config.rangeStrategy = 'widen'; @@ -446,8 +509,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.2.x - 1.4.x', updateType: 'minor' }, + ]); }); it('upgrades shorthand major ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -455,8 +519,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1', updateType: 'major' }, + ]); }); it('upgrades shorthand minor ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -464,8 +529,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.4', updateType: 'minor' }, + ]); }); it('upgrades multiple tilde ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -473,8 +539,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '~0.9.0', updateType: 'minor' }, + { newValue: '~1.4.0', updateType: 'major' }, + ]); }); it('upgrades multiple caret ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -482,8 +550,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '^0.9.0', updateType: 'minor' }, + { newValue: '^1.0.0', updateType: 'major' }, + ]); }); it('supports complex ranges', async () => { config.rangeStrategy = 'widen'; @@ -493,8 +563,10 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(2); - // FIXME: explicit assert condition - expect(res.updates[0]).toMatchSnapshot(); + expect(res.updates[0]).toMatchSnapshot({ + newValue: '^0.7.0 || ^0.8.0 || ^0.9.0', + updateType: 'minor', + }); }); it('supports complex major ranges', async () => { config.rangeStrategy = 'widen'; @@ -505,8 +577,12 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://registry.npmjs.org') .get('/webpack') .reply(200, webpackJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { + newValue: '^1.0.0 || ^2.0.0 || ^3.0.0', + updateType: 'major', + }, + ]); }); it('supports complex major hyphen ranges', async () => { config.rangeStrategy = 'widen'; @@ -517,8 +593,9 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://registry.npmjs.org') .get('/webpack') .reply(200, webpackJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.x - 3.x', updateType: 'major' }, + ]); }); it('widens .x OR ranges', async () => { config.rangeStrategy = 'widen'; @@ -529,8 +606,9 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://registry.npmjs.org') .get('/webpack') .reply(200, webpackJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.x || 2.x || 3.x', updateType: 'major' }, + ]); }); it('widens stanndalone major OR ranges', async () => { config.rangeStrategy = 'widen'; @@ -541,8 +619,9 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://registry.npmjs.org') .get('/webpack') .reply(200, webpackJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1 || 2 || 3', updateType: 'major' }, + ]); }); it('supports complex tilde ranges', async () => { config.rangeStrategy = 'widen'; @@ -550,8 +629,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '~1.2.0 || ~1.3.0 || ~1.4.0', updateType: 'minor' }, + ]); }); it('returns nothing for greater than ranges', async () => { config.rangeStrategy = 'replace'; @@ -567,8 +647,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '<= 0.9.7', updateType: 'minor' }, + { newValue: '<= 1.4.1', updateType: 'major' }, + ]); }); it('upgrades less than ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -576,8 +658,10 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '< 0.9.8', updateType: 'minor' }, + { newValue: '< 1.4.2', updateType: 'major' }, + ]); }); it('upgrades less than major ranges', async () => { config.rangeStrategy = 'replace'; @@ -585,8 +669,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '< 2', updateType: 'major' }, + ]); }); it('upgrades less than equal minor ranges', async () => { config.rangeStrategy = 'replace'; @@ -594,8 +679,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '<= 1.4', updateType: 'minor' }, + ]); }); it('upgrades equal minor ranges', async () => { config.rangeStrategy = 'replace'; @@ -603,8 +689,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '=1.4.1', updateType: 'minor' }, + ]); }); it('upgrades less than equal major ranges', async () => { config.rangeStrategy = 'replace'; @@ -613,8 +700,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '<= 2', updateType: 'major' }, + ]); }); it('upgrades major less than equal ranges', async () => { config.rangeStrategy = 'replace'; @@ -624,7 +712,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); - expect(res.updates[0].newValue).toEqual('<= 1.4.1'); + expect(res.updates[0].newValue).toBe('<= 1.4.1'); }); it('upgrades major less than ranges without pinning', async () => { config.rangeStrategy = 'replace'; @@ -634,7 +722,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); - expect(res.updates[0].newValue).toEqual('< 2.0.0'); + expect(res.updates[0].newValue).toBe('< 2.0.0'); }); it('upgrades major greater than less than ranges without pinning', async () => { config.rangeStrategy = 'widen'; @@ -644,7 +732,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); - expect(res.updates[0].newValue).toEqual('>= 0.5.0 < 2.0.0'); + expect(res.updates[0].newValue).toBe('>= 0.5.0 < 2.0.0'); }); it('upgrades minor greater than less than ranges without pinning', async () => { config.rangeStrategy = 'widen'; @@ -654,8 +742,8 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); - expect(res.updates[0].newValue).toEqual('>= 0.5.0 <0.10'); - expect(res.updates[1].newValue).toEqual('>= 0.5.0 <1.5'); + expect(res.updates[0].newValue).toBe('>= 0.5.0 <0.10'); + expect(res.updates[1].newValue).toBe('>= 0.5.0 <1.5'); }); it('upgrades minor greater than less than equals ranges without pinning', async () => { config.rangeStrategy = 'widen'; @@ -665,8 +753,8 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); - expect(res.updates[0].newValue).toEqual('>= 0.5.0 <= 0.9.7'); - expect(res.updates[1].newValue).toEqual('>= 0.5.0 <= 1.4.1'); + expect(res.updates[0].newValue).toBe('>= 0.5.0 <= 0.9.7'); + expect(res.updates[1].newValue).toBe('>= 0.5.0 <= 1.4.1'); }); it('rejects reverse ordered less than greater than', async () => { config.rangeStrategy = 'widen'; @@ -675,8 +763,7 @@ describe('workers/repository/process/lookup/index', () => { config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res.updates).toMatchSnapshot(); + expect(res.updates).toMatchSnapshot([]); }); it('supports > latest versions if configured', async () => { config.respectLatest = false; @@ -684,8 +771,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '2.0.3', updateType: 'major' }, + ]); }); it('should ignore unstable versions if the current version is stable', async () => { config.currentValue = '2.5.16'; @@ -715,8 +803,9 @@ describe('workers/repository/process/lookup/index', () => { }, ], }); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '2.0.0', updateType: 'major' }, + ]); }); it('should return pendingChecks', async () => { @@ -746,7 +835,7 @@ describe('workers/repository/process/lookup/index', () => { }); const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newVersion).toEqual('1.4.6'); + expect(res.updates[0].newVersion).toBe('1.4.6'); expect(res.updates[0].pendingChecks).toBeTrue(); }); @@ -777,7 +866,7 @@ describe('workers/repository/process/lookup/index', () => { }); const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newVersion).toEqual('1.4.5'); + expect(res.updates[0].newVersion).toBe('1.4.5'); expect(res.updates[0].pendingVersions).toHaveLength(1); }); @@ -794,7 +883,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newValue).toEqual('2.5.17-beta.0'); + expect(res.updates[0].newValue).toBe('2.5.17-beta.0'); }); it('should allow unstable versions if the current version is unstable', async () => { config.currentValue = '3.1.0-dev.20180731'; @@ -807,7 +896,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newValue).toEqual('3.1.0-dev.20180813'); + expect(res.updates[0].newValue).toBe('3.1.0-dev.20180813'); }); it('should not jump unstable versions', async () => { config.currentValue = '3.0.1-insiders.20180726'; @@ -820,7 +909,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newValue).toEqual('3.0.1'); + expect(res.updates[0].newValue).toBe('3.0.1'); }); it('should update pinned versions if updatePinnedDependencies=true', async () => { @@ -835,7 +924,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newValue).toEqual('0.0.35'); + expect(res.updates[0].newValue).toBe('0.0.35'); }); it('should not update pinned versions if updatePinnedDependencies=false', async () => { @@ -863,7 +952,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newValue).toEqual('3.0.1-insiders.20180726'); + expect(res.updates[0].newValue).toBe('3.0.1-insiders.20180726'); }); it('should roll back to dist-tag if current version is higher', async () => { config.currentValue = '3.1.0-dev.20180813'; @@ -878,7 +967,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newValue).toEqual('3.0.1-insiders.20180726'); + expect(res.updates[0].newValue).toBe('3.0.1-insiders.20180726'); }); it('should jump unstable versions if followTag', async () => { config.currentValue = '3.0.0-insiders.20180706'; @@ -892,7 +981,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); - expect(res.updates[0].newValue).toEqual('3.0.1-insiders.20180726'); + expect(res.updates[0].newValue).toBe('3.0.1-insiders.20180726'); }); it('should update nothing if current version is dist-tag', async () => { config.currentValue = '3.0.1-insiders.20180726'; @@ -919,7 +1008,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(0); expect(res.warnings).toHaveLength(1); - expect(res.warnings[0].message).toEqual( + expect(res.warnings[0].message).toBe( "Can't find version with tag foo for typescript" ); }); @@ -943,8 +1032,9 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://registry.npmjs.org') .get('/@types%2Fhelmet') .reply(200, helmetJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '^0.0.35', updateType: 'patch' }, + ]); }); it('should downgrade from missing versions', async () => { config.currentValue = '1.16.1'; @@ -1000,8 +1090,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '^1.4.1', updateType: 'minor' }, + ]); }); it('supports in-range tilde updates', async () => { config.rangeStrategy = 'bump'; @@ -1010,8 +1101,10 @@ describe('workers/repository/process/lookup/index', () => { config.separateMinorPatch = true; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '~1.0.1', updateType: 'patch' }, + { newValue: '~1.4.1', updateType: 'minor' }, + ]); }); it('supports in-range tilde patch updates', async () => { config.rangeStrategy = 'bump'; @@ -1020,8 +1113,10 @@ describe('workers/repository/process/lookup/index', () => { config.separateMinorPatch = true; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '~1.0.1', updateType: 'patch' }, + { newValue: '~1.4.1', updateType: 'minor' }, + ]); }); it('supports in-range gte updates', async () => { config.rangeStrategy = 'bump'; @@ -1029,8 +1124,9 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '>=1.4.1', updateType: 'minor' }, + ]); }); it('supports majorgte updates', async () => { config.rangeStrategy = 'bump'; @@ -1039,8 +1135,9 @@ describe('workers/repository/process/lookup/index', () => { config.datasource = datasourceNpmId; config.separateMajorMinor = false; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '>=1.4.1', updateType: 'major' }, + ]); }); it('rejects in-range unsupported operator', async () => { config.rangeStrategy = 'bump'; @@ -1048,8 +1145,7 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); it('rejects non-fully specified in-range updates', async () => { config.rangeStrategy = 'bump'; @@ -1057,8 +1153,7 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); it('rejects complex range in-range updates', async () => { config.rangeStrategy = 'bump'; @@ -1066,8 +1161,7 @@ describe('workers/repository/process/lookup/index', () => { config.depName = 'q'; config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); it('replaces non-range in-range updates', async () => { config.depName = 'q'; @@ -1076,8 +1170,9 @@ describe('workers/repository/process/lookup/index', () => { config.rangeStrategy = 'bump'; config.currentValue = '1.0.0'; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([ + { newValue: '1.4.1', updateType: 'minor' }, + ]); }); it('handles github 404', async () => { config.depName = 'foo'; @@ -1085,8 +1180,7 @@ describe('workers/repository/process/lookup/index', () => { config.packageFile = 'package.json'; config.currentValue = '1.0.0'; httpMock.scope('https://pypi.org').get('/pypi/foo/json').reply(404); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); it('handles pypi 404', async () => { config.depName = 'foo'; @@ -1097,8 +1191,7 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://api.github.com') .get('/repos/some/repo/git/refs/tags?per_page=100') .reply(404); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); it('handles packagist', async () => { config.depName = 'foo/bar'; @@ -1110,16 +1203,14 @@ describe('workers/repository/process/lookup/index', () => { .scope('https://packagist.org') .get('/packages/foo/bar.json') .reply(404); - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); it('handles unknown datasource', async () => { config.depName = 'foo'; config.datasource = 'typo'; config.packageFile = 'package.json'; config.currentValue = '1.0.0'; - // FIXME: explicit assert condition - expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot(); + expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); it('handles PEP440', async () => { config.manager = 'pip_requirements'; @@ -1134,8 +1225,11 @@ describe('workers/repository/process/lookup/index', () => { config.datasource = datasourceNpmId; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res.updates).toMatchSnapshot(); + expect(res.updates).toMatchSnapshot([ + { newValue: '==0.9.4', updateType: 'pin' }, + { newValue: '==0.9.7', updateType: 'patch' }, + { newValue: '==1.4.1', updateType: 'major' }, + ]); }); it('returns complex object', async () => { config.currentValue = '1.3.0'; @@ -1159,7 +1253,7 @@ describe('workers/repository/process/lookup/index', () => { .reply(200, returnJson); const res = await lookup.lookupUpdates(config); expect(res).toMatchSnapshot(); - expect(res.updates[0].newVersion).toEqual('1.4.0'); + expect(res.updates[0].newVersion).toBe('1.4.0'); }); it('is deprecated', async () => { config.currentValue = '1.3.0'; @@ -1178,22 +1272,20 @@ describe('workers/repository/process/lookup/index', () => { .reply(200, returnJson); const res = await lookup.lookupUpdates(config); expect(res).toMatchSnapshot(); - expect(res.updates[0].newVersion).toEqual('1.4.1'); + expect(res.updates[0].newVersion).toBe('1.4.1'); }); it('skips unsupported values', async () => { config.currentValue = 'alpine'; config.depName = 'node'; config.datasource = datasourceDockerId; const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ skipReason: 'invalid-value' }); }); it('skips undefined values', async () => { config.depName = 'node'; config.datasource = datasourceDockerId; const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ skipReason: 'invalid-value' }); }); it('handles digest pin', async () => { config.currentValue = '8.0.0'; @@ -1213,31 +1305,93 @@ describe('workers/repository/process/lookup/index', () => { docker.getDigest.mockResolvedValueOnce('sha256:abcdef1234567890'); docker.getDigest.mockResolvedValueOnce('sha256:0123456789abcdef'); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + currentVersion: '8.0.0', + isSingleVersion: true, + updates: [ + { + newDigest: 'sha256:abcdef1234567890', + newValue: '8.1.0', + updateType: 'minor', + }, + { + newDigest: 'sha256:0123456789abcdef', + newValue: '8.0.0', + updateType: 'pin', + }, + ], + }); }); - ['8.1.0', '8.1', '8'].forEach((currentValue) => { - it('skips uncompatible versions for ' + currentValue, async () => { - config.currentValue = currentValue; - config.depName = 'node'; - config.versioning = dockerVersioningId; - config.datasource = datasourceDockerId; - docker.getReleases.mockResolvedValueOnce({ - releases: [ - { version: '8.1.0' }, - { version: '8.1.5' }, - { version: '8.1' }, - { version: '8.2.0' }, - { version: '8.2.5' }, - { version: '8.2' }, - { version: '8' }, - { version: '9.0' }, - { version: '9' }, - ], - }); - const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + it('skips uncompatible versions for 8.1.0', async () => { + config.currentValue = '8.1.0'; + config.depName = 'node'; + config.versioning = dockerVersioningId; + config.datasource = datasourceDockerId; + docker.getReleases.mockResolvedValueOnce({ + releases: [ + { version: '8.1.0' }, + { version: '8.1.5' }, + { version: '8.1' }, + { version: '8.2.0' }, + { version: '8.2.5' }, + { version: '8.2' }, + { version: '8' }, + { version: '9.0' }, + { version: '9' }, + ], + }); + const res = await lookup.lookupUpdates(config); + expect(res).toMatchSnapshot({ + updates: [{ newValue: '8.2.5', updateType: 'minor' }], + }); + }); + it('skips uncompatible versions for 8.1', async () => { + config.currentValue = '8.1'; + config.depName = 'node'; + config.versioning = dockerVersioningId; + config.datasource = datasourceDockerId; + docker.getReleases.mockResolvedValueOnce({ + releases: [ + { version: '8.1.0' }, + { version: '8.1.5' }, + { version: '8.1' }, + { version: '8.2.0' }, + { version: '8.2.5' }, + { version: '8.2' }, + { version: '8' }, + { version: '9.0' }, + { version: '9' }, + ], + }); + const res = await lookup.lookupUpdates(config); + expect(res).toMatchSnapshot({ + updates: [ + { newValue: '8.2', updateType: 'minor' }, + { newValue: '9.0', updateType: 'major' }, + ], + }); + }); + it('skips uncompatible versions for 8', async () => { + config.currentValue = '8'; + config.depName = 'node'; + config.versioning = dockerVersioningId; + config.datasource = datasourceDockerId; + docker.getReleases.mockResolvedValueOnce({ + releases: [ + { version: '8.1.0' }, + { version: '8.1.5' }, + { version: '8.1' }, + { version: '8.2.0' }, + { version: '8.2.5' }, + { version: '8.2' }, + { version: '8' }, + { version: '9.0' }, + { version: '9' }, + ], + }); + const res = await lookup.lookupUpdates(config); + expect(res).toMatchSnapshot({ + updates: [{ newValue: '9', updateType: 'major' }], }); }); it('handles digest pin for up to date version', async () => { @@ -1257,8 +1411,15 @@ describe('workers/repository/process/lookup/index', () => { }); docker.getDigest.mockResolvedValueOnce('sha256:abcdef1234567890'); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + updates: [ + { + newDigest: 'sha256:abcdef1234567890', + newValue: '8.1.0', + updateType: 'pin', + }, + ], + }); }); it('handles digest pin for non-version', async () => { config.currentValue = 'alpine'; @@ -1280,8 +1441,15 @@ describe('workers/repository/process/lookup/index', () => { }); docker.getDigest.mockResolvedValueOnce('sha256:abcdef1234567890'); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + updates: [ + { + newDigest: 'sha256:abcdef1234567890', + newValue: 'alpine', + updateType: 'pin', + }, + ], + }); }); it('handles digest lookup failure', async () => { config.currentValue = 'alpine'; @@ -1324,8 +1492,20 @@ describe('workers/repository/process/lookup/index', () => { docker.getDigest.mockResolvedValueOnce('sha256:abcdef1234567890'); docker.getDigest.mockResolvedValueOnce('sha256:0123456789abcdef'); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + updates: [ + { + newDigest: 'sha256:abcdef1234567890', + newValue: '8.1.0', + updateType: 'minor', + }, + { + newDigest: 'sha256:0123456789abcdef', + newValue: '8.0.0', + updateType: 'digest', + }, + ], + }); }); it('handles digest update for non-version', async () => { config.currentValue = 'alpine'; @@ -1348,8 +1528,15 @@ describe('workers/repository/process/lookup/index', () => { }); docker.getDigest.mockResolvedValueOnce('sha256:abcdef1234567890'); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + updates: [ + { + newDigest: 'sha256:abcdef1234567890', + newValue: 'alpine', + updateType: 'digest', + }, + ], + }); }); it('handles git submodule update', async () => { jest.mock('../../../../datasource/git-refs', () => ({ @@ -1381,8 +1568,15 @@ describe('workers/repository/process/lookup/index', () => { config.currentDigest = 'some-digest'; const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition - expect(res).toMatchSnapshot(); + expect(res).toMatchSnapshot({ + updates: [ + { + newDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', + updateType: 'digest', + }, + ], + versioning: 'git', + }); }); it('handles sourceUrl packageRules with version restrictions', async () => { config.currentValue = '0.9.99'; @@ -1396,7 +1590,21 @@ describe('workers/repository/process/lookup/index', () => { ]; httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); const res = await lookup.lookupUpdates(config); - // FIXME: explicit assert condition + expect(res).toMatchSnapshot({ + sourceUrl: 'https://github.com/kriskowal/q', + updates: [{ newValue: '1.3.0', updateType: 'major' }], + }); + }); + + it('handles replacements', async () => { + config.currentValue = '1.4.1'; + config.depName = 'q'; + // This config is normally set when packageRules are applied + config.replacementName = 'r'; + config.replacementVersion = '2.0.0'; + config.datasource = datasourceNpmId; + httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); + const res = await lookup.lookupUpdates(config); expect(res).toMatchSnapshot(); }); }); diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index ad0d46dd9c9619..d1417de1903fd1 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import { mergeChildConfig } from '../../../../config'; import type { ValidationMessage } from '../../../../config/types'; import { CONFIG_VALIDATION } from '../../../../constants/error-messages'; @@ -44,6 +45,7 @@ export async function lookupUpdates( isVulnerabilityAlert, updatePinnedDependencies, } = config; + const unconstrainedValue = lockedVersion && is.undefined(currentValue); const res: UpdateResult = { updates: [], warnings: [], @@ -63,7 +65,7 @@ export async function lookupUpdates( return res; } const isValid = currentValue && versioning.isValid(currentValue); - if (isValid) { + if (unconstrainedValue || isValid) { if ( !updatePinnedDependencies && versioning.isSingleVersion(currentValue) @@ -88,6 +90,7 @@ export async function lookupUpdates( logger.debug({ dependency: depName }, 'Found deprecationMessage'); res.deprecationMessage = dependency.deprecationMessage; } + res.sourceUrl = dependency?.sourceUrl; if (dependency.sourceDirectory) { res.sourceDirectory = dependency.sourceDirectory; @@ -112,7 +115,7 @@ export async function lookupUpdates( // Reapply package rules in case we missed something from sourceUrl config = applyPackageRules({ ...config, sourceUrl: res.sourceUrl }); if (followTag) { - const taggedVersion = dependency.tags[followTag]; + const taggedVersion = dependency.tags?.[followTag]; if (!taggedVersion) { res.warnings.push({ topic: depName, @@ -128,8 +131,8 @@ export async function lookupUpdates( ); } // Check that existing constraint can be satisfied - const allSatisfyingVersions = allVersions.filter((v) => - versioning.matches(v.version, currentValue) + const allSatisfyingVersions = allVersions.filter( + (v) => unconstrainedValue || versioning.matches(v.version, currentValue) ); if (rollbackPrs && !allSatisfyingVersions.length) { const rollback = getRollbackUpdate(config, allVersions, versioning); @@ -144,6 +147,17 @@ export async function lookupUpdates( res.updates.push(rollback); } let rangeStrategy = getRangeStrategy(config); + if (dependency.replacementName && dependency.replacementVersion) { + res.updates.push({ + updateType: 'replacement', + newName: dependency.replacementName, + newValue: versioning.getNewValue({ + currentValue, + newVersion: dependency.replacementVersion, + rangeStrategy, + }), + }); + } // istanbul ignore next if ( isVulnerabilityAlert && @@ -155,16 +169,22 @@ export async function lookupUpdates( const nonDeprecatedVersions = dependency.releases .filter((release) => !release.isDeprecated) .map((release) => release.version); - const currentVersion = + let currentVersion: string; + if (rangeStrategy === 'update-lockfile') { + currentVersion = lockedVersion; + } + currentVersion ??= getCurrentVersion( - config, + currentValue, + lockedVersion, versioning, rangeStrategy, latestVersion, nonDeprecatedVersions ) || getCurrentVersion( - config, + currentValue, + lockedVersion, versioning, rangeStrategy, latestVersion, @@ -176,6 +196,7 @@ export async function lookupUpdates( } res.currentVersion = currentVersion; if ( + currentValue && currentVersion && rangeStrategy === 'pin' && !versioning.isSingleVersion(currentValue) @@ -192,21 +213,22 @@ export async function lookupUpdates( newMajor: versioning.getMajor(currentVersion), }); } - let filterStart = currentVersion; - if (lockedVersion && rangeStrategy === 'update-lockfile') { - // Look for versions greater than the current locked version that still satisfy the package.json range - filterStart = lockedVersion; + // istanbul ignore if + if (!versioning.isVersion(currentVersion)) { + res.skipReason = SkipReason.InvalidVersion; + return res; } // Filter latest, unstable, etc let filteredReleases = filterVersions( config, - filterStart, + currentVersion, latestVersion, allVersions, versioning - ).filter((v) => - // Leave only compatible versions - versioning.isCompatible(v.version, currentValue) + ).filter( + (v) => + // Leave only compatible versions + unconstrainedValue || versioning.isCompatible(v.version, currentValue) ); if (isVulnerabilityAlert) { filteredReleases = filteredReleases.slice(0, 1); @@ -219,10 +241,12 @@ export async function lookupUpdates( release.version, versioning ); - if (buckets[bucket]) { - buckets[bucket].push(release); - } else { - buckets[bucket] = [release]; + if (is.string(bucket)) { + if (buckets[bucket]) { + buckets[bucket].push(release); + } else { + buckets[bucket] = [release]; + } } } const depResultConfig = mergeChildConfig(config, res); @@ -258,7 +282,7 @@ export async function lookupUpdates( } if (!update.newValue || update.newValue === currentValue) { if (!lockedVersion) { - continue; // eslint-disable-line no-continue + continue; } // istanbul ignore if if (rangeStrategy === 'bump') { @@ -266,7 +290,7 @@ export async function lookupUpdates( { depName, currentValue, lockedVersion, newVersion }, 'Skipping bump because newValue is the same' ); - continue; // eslint-disable-line no-continue + continue; } res.isSingleVersion = true; } @@ -359,6 +383,7 @@ export async function lookupUpdates( rollbackPrs, isVulnerabilityAlert, updatePinnedDependencies, + unconstrainedValue, err, }, 'lookupUpdates error' diff --git a/lib/workers/repository/process/write.spec.ts b/lib/workers/repository/process/write.spec.ts index e818bc2aaf3f45..067e1fe952179c 100644 --- a/lib/workers/repository/process/write.spec.ts +++ b/lib/workers/repository/process/write.spec.ts @@ -49,7 +49,7 @@ describe('workers/repository/process/write', () => { result: BranchResult.Automerged, }); const res = await writeUpdates(config, branches); - expect(res).toEqual('automerged'); + expect(res).toBe('automerged'); expect(branchWorker.processBranch).toHaveBeenCalledTimes(4); }); it('increments branch counter', async () => { diff --git a/lib/workers/repository/updates/branch-name.spec.ts b/lib/workers/repository/updates/branch-name.spec.ts index 15ca57ac5cc9cc..a1436ee7c3b4b2 100644 --- a/lib/workers/repository/updates/branch-name.spec.ts +++ b/lib/workers/repository/updates/branch-name.spec.ts @@ -12,7 +12,7 @@ describe('workers/repository/updates/branch-name', () => { }, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('some-group-name-grouptopic'); + expect(upgrade.branchName).toBe('some-group-name-grouptopic'); }); it('uses groupSlug if defined', () => { const upgrade: RenovateConfig = { @@ -24,7 +24,7 @@ describe('workers/repository/updates/branch-name', () => { }, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('some-group-slug-grouptopic'); + expect(upgrade.branchName).toBe('some-group-slug-grouptopic'); }); it('separates major with groups', () => { const upgrade: RenovateConfig = { @@ -40,7 +40,7 @@ describe('workers/repository/updates/branch-name', () => { }, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('major-2-some-group-slug-grouptopic'); + expect(upgrade.branchName).toBe('major-2-some-group-slug-grouptopic'); }); it('uses single major with groups', () => { const upgrade: RenovateConfig = { @@ -56,7 +56,7 @@ describe('workers/repository/updates/branch-name', () => { }, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('major-some-group-slug-grouptopic'); + expect(upgrade.branchName).toBe('major-some-group-slug-grouptopic'); }); it('separates patch groups and uses update topic', () => { const upgrade: RenovateConfig = { @@ -71,7 +71,7 @@ describe('workers/repository/updates/branch-name', () => { group: {}, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual( + expect(upgrade.branchName).toBe( 'update-branch-patch-some-group-slug-update-topic' ); }); @@ -83,7 +83,7 @@ describe('workers/repository/updates/branch-name', () => { group: {}, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('dep'); + expect(upgrade.branchName).toBe('dep'); }); it('separates patches when separateMinorPatch=true', () => { const upgrade: RenovateConfig = { @@ -102,7 +102,7 @@ describe('workers/repository/updates/branch-name', () => { group: {}, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('renovate/lodash-4.17.x'); + expect(upgrade.branchName).toBe('renovate/lodash-4.17.x'); }); it('does not separate patches when separateMinorPatch=false', () => { const upgrade: RenovateConfig = { @@ -121,7 +121,7 @@ describe('workers/repository/updates/branch-name', () => { group: {}, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('renovate/lodash-4.x'); + expect(upgrade.branchName).toBe('renovate/lodash-4.x'); }); it('realistic defaults', () => { @@ -136,7 +136,7 @@ describe('workers/repository/updates/branch-name', () => { group: {}, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('renovate/jest-42.x'); + expect(upgrade.branchName).toBe('renovate/jest-42.x'); }); it('hashedBranchLength hashing', () => { @@ -152,7 +152,7 @@ describe('workers/repository/updates/branch-name', () => { group: {}, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('dep-df9ca0f348'); + expect(upgrade.branchName).toBe('dep-df9ca0f348'); }); it('hashedBranchLength hashing with group name', () => { @@ -170,7 +170,7 @@ describe('workers/repository/updates/branch-name', () => { }, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('dep-df9ca0f34833f3e0'); + expect(upgrade.branchName).toBe('dep-df9ca0f34833f3e0'); }); it('hashedBranchLength too short', () => { @@ -188,7 +188,7 @@ describe('workers/repository/updates/branch-name', () => { }, }; generateBranchName(upgrade); - expect(upgrade.branchName).toEqual('dep-df9ca0'); + expect(upgrade.branchName).toBe('dep-df9ca0'); }); it('enforces valid git branch name', () => { @@ -220,7 +220,7 @@ describe('workers/repository/updates/branch-name', () => { }, { upgrade: { branchName: 'renovate/~bad-branch-name2' }, - expectedBranchName: 'renovate/-bad-branch-name2', + expectedBranchName: 'renovate/bad-branch-name2', }, { upgrade: { branchName: 'renovate/bad-branch-^-name3' }, @@ -258,6 +258,10 @@ describe('workers/repository/updates/branch-name', () => { upgrade: { branchName: 'renovate/bad--branch---name11' }, expectedBranchName: 'renovate/bad-branch-name11', }, + { + upgrade: { branchName: 'renovate-/[start]-something-[end]' }, + expectedBranchName: 'renovate/start-something-end', + }, ]; fixtures.forEach((fixture) => { generateBranchName(fixture.upgrade); diff --git a/lib/workers/repository/updates/branch-name.ts b/lib/workers/repository/updates/branch-name.ts index 32211dca8760a6..7e45f0802df0ef 100644 --- a/lib/workers/repository/updates/branch-name.ts +++ b/lib/workers/repository/updates/branch-name.ts @@ -24,10 +24,11 @@ function cleanBranchName(branchName: string): string { .replace(regEx(/\/\./g), '/') // leading dot after slash .replace(regEx(/\s/g), '') // whitespace .replace(regEx(/[[\]?:\\^~]/g), '-') // massage out all these characters: : ? [ \ ^ ~ + .replace(regEx(/(^|\/)-+/g), '$1') // leading dashes + .replace(regEx(/-+(\/|$)/g), '$1') // trailing dashes .replace(RE_MULTIPLE_DASH, '-'); // chained dashes } -/* eslint-disable no-param-reassign */ export function generateBranchName(update: RenovateConfig): void { // Check whether to use a group name if (update.groupName) { diff --git a/lib/workers/repository/updates/flatten.spec.ts b/lib/workers/repository/updates/flatten.spec.ts index 9d56625643e473..84c3a997eb74ca 100644 --- a/lib/workers/repository/updates/flatten.spec.ts +++ b/lib/workers/repository/updates/flatten.spec.ts @@ -66,6 +66,16 @@ describe('workers/repository/updates/flatten', () => { updateTypes: ['pin'], updates: [{ newValue: '2.0.0' }], }, + { + depName: 'abc', + updates: [ + { + newName: 'def', + newValue: '2.0.0', + updateType: 'replacement', + }, + ], + }, ], }, { @@ -131,7 +141,7 @@ describe('workers/repository/updates/flatten', () => { ], }; const res = await flattenUpdates(config, packageFiles); - expect(res).toHaveLength(13); + expect(res).toHaveLength(14); expect(res.filter((update) => update.sourceRepoSlug)).toHaveLength(3); expect( res.filter((r) => r.updateType === 'lockFileMaintenance') diff --git a/lib/workers/repository/updates/flatten.ts b/lib/workers/repository/updates/flatten.ts index 22f3070e48c637..5f3bf1e64df39d 100644 --- a/lib/workers/repository/updates/flatten.ts +++ b/lib/workers/repository/updates/flatten.ts @@ -15,18 +15,25 @@ import { generateBranchName } from './branch-name'; const upper = (str: string): string => str.charAt(0).toUpperCase() + str.substr(1); +function sanitizeDepName(depName: string): string { + return depName + .replace('@types/', '') + .replace('@', '') + .replace(regEx(/\//g), '-') // TODO #12071 + .replace(regEx(/\s+/g), '-') // TODO #12071 + .replace(regEx(/-+/), '-') + .toLowerCase(); +} + export function applyUpdateConfig(input: BranchUpgradeConfig): any { const updateConfig = { ...input }; delete updateConfig.packageRules; // TODO: Remove next line once #8075 is complete updateConfig.depNameSanitized = updateConfig.depName - ? updateConfig.depName - .replace('@types/', '') - .replace('@', '') - .replace(regEx(/\//g), '-') // TODO #12071 - .replace(regEx(/\s+/g), '-') // TODO #12071 - .replace(regEx(/-+/), '-') - .toLowerCase() + ? sanitizeDepName(updateConfig.depName) + : undefined; + updateConfig.newNameSanitized = updateConfig.newName + ? sanitizeDepName(updateConfig.newName) : undefined; if (updateConfig.sourceUrl) { const parsedSourceUrl = parseUrl(updateConfig.sourceUrl); @@ -53,6 +60,7 @@ export async function flattenUpdates( 'pin', 'digest', 'lockFileMaintenance', + 'replacement', ]; for (const [manager, files] of Object.entries(packageFiles)) { const managerConfig = getManagerConfig(config, manager); diff --git a/lib/workers/repository/updates/generate.spec.ts b/lib/workers/repository/updates/generate.spec.ts index 0a767cec1fc6f4..48da8cb6b820cd 100644 --- a/lib/workers/repository/updates/generate.spec.ts +++ b/lib/workers/repository/updates/generate.spec.ts @@ -146,7 +146,7 @@ describe('workers/repository/updates/generate', () => { const res = generateBranchConfig(branch); expect(res.foo).toBe(2); expect(res.groupName).toBeDefined(); - expect(res.releaseTimestamp).toEqual('2017-02-07T20:01:41+00:00'); + expect(res.releaseTimestamp).toBe('2017-02-07T20:01:41+00:00'); expect(res.automerge).toBeFalse(); expect(res.constraints).toEqual({ foo: '1.0.0', @@ -191,7 +191,7 @@ describe('workers/repository/updates/generate', () => { expect(res.singleVersion).toBeUndefined(); expect(res.recreateClosed).toBeTrue(); expect(res.groupName).toBeDefined(); - expect(res.releaseTimestamp).toEqual('2017-02-08T20:01:41+00:00'); + expect(res.releaseTimestamp).toBe('2017-02-08T20:01:41+00:00'); }); it('groups multiple digest updates', () => { const branch = [ @@ -285,7 +285,7 @@ describe('workers/repository/updates/generate', () => { }), ]; const res = generateBranchConfig(branch); - expect(res.prTitle).toEqual( + expect(res.prTitle).toBe( 'chore(package): update dependency some-dep to v1.2.0' ); }); @@ -309,9 +309,7 @@ describe('workers/repository/updates/generate', () => { }), ]; const res = generateBranchConfig(branch); - expect(res.prTitle).toEqual( - 'chore(): update dependency some-dep to v1.2.0' - ); + expect(res.prTitle).toBe('chore(): update dependency some-dep to v1.2.0'); }); it('scopes monorepo commits with nested package files using parent directory', () => { const branch = [ @@ -334,7 +332,7 @@ describe('workers/repository/updates/generate', () => { }), ]; const res = generateBranchConfig(branch); - expect(res.prTitle).toEqual( + expect(res.prTitle).toBe( 'chore(bar): update dependency some-dep to v1.2.0' ); }); @@ -358,7 +356,7 @@ describe('workers/repository/updates/generate', () => { }), ]; const res = generateBranchConfig(branch); - expect(res.prTitle).toEqual( + expect(res.prTitle).toBe( 'chore(foo/bar): update dependency some-dep to v1.2.0' ); }); diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts index 4ff74aff119382..49c9570f2577f3 100644 --- a/lib/workers/repository/updates/generate.ts +++ b/lib/workers/repository/updates/generate.ts @@ -92,7 +92,6 @@ export function generateBranchConfig( toVersions.length > 1 || (!toVersions[0] && newValue.length > 1); if (newValue.length > 1 && !groupEligible) { - // eslint-disable-next-line no-param-reassign branchUpgrades[0].commitMessageExtra = `to v${toVersions[0]}`; } const typesGroup = @@ -173,6 +172,10 @@ export function generateBranchConfig( upgrade.commitMessage = template.compile(upgrade.commitMessage, upgrade); // istanbul ignore if if (upgrade.commitMessage !== sanitize(upgrade.commitMessage)) { + logger.debug( + { branchName: config.branchName }, + 'Secrets exposed in commit message' + ); throw new Error(CONFIG_SECRETS_EXPOSED); } upgrade.commitMessage = upgrade.commitMessage.trim(); // Trim exterior whitespace @@ -203,6 +206,10 @@ export function generateBranchConfig( .replace(regEx(/\s+/g), ' '); // TODO #12071 // istanbul ignore if if (upgrade.prTitle !== sanitize(upgrade.prTitle)) { + logger.debug( + { branchName: config.branchName }, + 'Secrets were exposed in PR title' + ); throw new Error(CONFIG_SECRETS_EXPOSED); } if (upgrade.toLowerCase) { @@ -235,10 +242,10 @@ export function generateBranchConfig( const existingStamp = DateTime.fromISO(releaseTimestamp); const upgradeStamp = DateTime.fromISO(upgrade.releaseTimestamp); if (upgradeStamp > existingStamp) { - releaseTimestamp = upgrade.releaseTimestamp; // eslint-disable-line + releaseTimestamp = upgrade.releaseTimestamp; } } else { - releaseTimestamp = upgrade.releaseTimestamp; // eslint-disable-line + releaseTimestamp = upgrade.releaseTimestamp; } } } diff --git a/lib/workers/types.ts b/lib/workers/types.ts index 401464f9664541..eeb658f305d3fd 100644 --- a/lib/workers/types.ts +++ b/lib/workers/types.ts @@ -14,9 +14,11 @@ import type { PackageFile, } from '../manager/types'; import type { PlatformPrOptions } from '../platform/types'; -import type { File } from '../util/git'; +import type { File } from '../util/git/types'; import type { MergeConfidence } from '../util/merge-confidence'; -import type { ChangeLogResult } from './pr/changelog/types'; +import type { ChangeLogRelease, ChangeLogResult } from './pr/changelog/types'; + +export type ReleaseWithNotes = Release & Partial; export interface BranchUpgradeConfig extends Merge, @@ -50,7 +52,7 @@ export interface BranchUpgradeConfig prBodyTemplate?: string; prPriority?: number; prTitle?: string; - releases?: Release[]; + releases?: ReleaseWithNotes[]; releaseTimestamp?: string; repoName?: string; minimumConfidence?: MergeConfidence; @@ -61,6 +63,7 @@ export interface BranchUpgradeConfig logJSON?: ChangeLogResult; + hasReleaseNotes?: boolean; homepage?: string; changelogUrl?: string; dependencyUrl?: string; diff --git a/package.json b/package.json index 2b1e530575197a..b04c4ea8b2690f 100644 --- a/package.json +++ b/package.json @@ -15,19 +15,21 @@ "create-json-schema": "node -r ts-node/register/transpile-only -- bin/create-json-schema.js && prettier --write \"renovate-schema.json\"", "debug": "node --inspect-brk -r ts-node/register/transpile-only -- lib/renovate.ts", "doc-fix": "run-s markdown-lint-fix prettier-fix", - "eslint": "eslint --ext .js,.mjs,.ts lib/ test/ tools/", - "eslint-fix": "eslint --ext .js,.mjs,.ts --fix lib/ test/ tools/", + "doc-fence-check": "node tools/check-fenced-code.mjs", + "eslint": "eslint --ext .js,.mjs,.ts lib/ test/ tools/ --report-unused-disable-directives", + "eslint-fix": "eslint --ext .js,.mjs,.ts --fix lib/ test/ tools/ --report-unused-disable-directives", "generate": "run-s generate:*", "generate:imports": "node tools/generate-imports.mjs", "git-check": "node tools/check-git-version.mjs", - "jest": "cross-env NODE_ENV=test LOG_LEVEL=fatal TZ=UTC node --expose-gc node_modules/jest/bin/jest.js --logHeapUsage", + "jest": "cross-env LOG_LEVEL=fatal node --expose-gc node_modules/jest/bin/jest.js --logHeapUsage", "jest-debug": "cross-env NODE_OPTIONS=--inspect-brk yarn jest --testTimeout=100000000", "jest-silent": "cross-env yarn jest --reporters jest-silent-reporter", - "lint": "run-s ls-lint eslint prettier markdown-lint git-check", + "lint": "run-s ls-lint eslint prettier markdown-lint git-check doc-fence-check", "lint-fix": "run-s eslint-fix prettier-fix markdown-lint-fix", "ls-lint": "ls-lint", "markdown-lint": "markdownlint-cli2", "markdown-lint-fix": "markdownlint-cli2-fix", + "null-check": "run-s generate:* \"tsc --noEmit -p tsconfig.strict.json {@}\"", "prepare": "run-s prepare:*", "prepare:husky": "husky install", "prepare:generate": "run-s generate:*", @@ -38,7 +40,7 @@ "prettier-fix": "prettier --write \"**/*.{ts,js,mjs,json,md,yml}\"", "release": "node tools/release.mjs", "start": "node -r ts-node/register/transpile-only -- lib/renovate.ts", - "test": "run-s lint test-schema type-check jest", + "test": "run-s lint test-schema type-check null-check jest", "test-dirty": "git diff --exit-code", "test-e2e": "npm pack && cd test/e2e && yarn install --no-lockfile --ignore-optional --prod && yarn test", "test-schema": "node -r ts-node/register/transpile-only -- test/json-schema.ts", @@ -106,7 +108,9 @@ "Mikhail Yakushin ", "Sebastian Poxhofer ", "Henry Sachs ", - "Arkadiusz Kosmala " + "Arkadiusz Kosmala ", + "Markus Siebert ", + "Sergey Vedmak " ], "license": "AGPL-3.0", "bugs": { @@ -122,16 +126,18 @@ "node": ">=14.15.0" }, "dependencies": { + "@aws-sdk/client-ec2": "3.35.0", "@aws-sdk/client-ecr": "3.38.0", "@breejs/later": "4.1.0", "@iarna/toml": "2.2.5", "@renovate/pep440": "1.0.0", + "@renovatebot/parser-utils": "1.0.0", "@renovatebot/ruby-semver": "1.0.0", "@sindresorhus/is": "4.2.0", "@yarnpkg/core": "2.4.0", "@yarnpkg/parsers": "2.4.1", "auth-header": "1.0.0", - "azure-devops-node-api": "11.0.1", + "azure-devops-node-api": "11.1.0", "bunyan": "1.8.15", "cacache": "15.3.0", "chalk": "4.1.2", @@ -156,24 +162,25 @@ "git-url-parse": "11.6.0", "github-url-from-git": "1.5.0", "global-agent": "2.2.0", - "got": "11.8.2", + "got": "11.8.3", "handlebars": "4.7.7", "handy-redis": "2.3.1", "hasha": "5.2.2", - "ignore": "5.1.8", + "ignore": "5.1.9", "ini": "2.0.0", "js-yaml": "4.1.0", "json-dup-key-validator": "1.0.3", "json-stringify-pretty-compact": "3.0.0", "json5": "2.2.0", - "luxon": "2.0.2", + "luxon": "2.1.1", "markdown-it": "12.2.0", "markdown-table": "2.0.0", "marshal": "0.5.2", "minimatch": "3.0.4", "moo": "0.5.1", + "nanoid": "3.1.30", "node-html-parser": "3.3.6", - "openpgp": "5.0.0", + "openpgp": "5.0.1", "p-all": "3.0.0", "p-map": "4.0.0", "p-queue": "6.6.2", @@ -187,10 +194,10 @@ "semver-stable": "3.0.0", "semver-utils": "1.1.4", "shlex": "2.1.0", - "shortid": "2.2.16", - "simple-git": "2.47.0", - "slugify": "1.6.1", + "simple-git": "2.47.1", + "slugify": "1.6.3", "traverse": "0.6.6", + "tslib": "2.3.1", "upath": "2.0.1", "url-join": "4.0.1", "validate-npm-package-name": "3.0.0", @@ -201,37 +208,38 @@ }, "devDependencies": { "@actions/core": "1.6.0", - "@jest/globals": "27.3.1", - "@jest/reporters": "27.3.1", - "@jest/test-result": "27.3.1", + "@aws-sdk/client-s3": "3.41.0", + "@jest/globals": "27.4.2", + "@jest/reporters": "27.4.2", + "@jest/test-result": "27.4.2", "@ls-lint/ls-lint": "1.10.0", - "@renovate/eslint-plugin": "https://github.com/renovatebot/eslint-plugin#v0.0.3", - "@semantic-release/exec": "6.0.1", + "@renovate/eslint-plugin": "https://github.com/renovatebot/eslint-plugin#v0.0.4", + "@semantic-release/exec": "6.0.2", "@types/auth-header": "1.0.2", - "@types/bunyan": "1.8.7", + "@types/bunyan": "1.8.8", "@types/cacache": "15.0.1", "@types/changelog-filename-regex": "2.0.0", "@types/clean-git-ref": "2.0.0", "@types/conventional-commits-detector": "1.0.0", - "@types/eslint": "7.28.2", + "@types/eslint": "7.29.0", "@types/fs-extra": "9.0.13", "@types/git-url-parse": "9.0.1", "@types/github-url-from-git": "1.5.1", "@types/global-agent": "2.1.1", "@types/ini": "1.3.31", - "@types/jest": "27.0.2", - "@types/js-yaml": "4.0.4", + "@types/jest": "27.0.3", + "@types/js-yaml": "4.0.5", "@types/json-dup-key-validator": "1.0.0", "@types/linkify-markdown": "1.0.1", - "@types/luxon": "2.0.5", + "@types/luxon": "2.0.7", "@types/markdown-it": "12.2.3", "@types/markdown-table": "2.0.0", "@types/marshal": "0.5.1", "@types/moo": "0.5.5", "@types/nock": "10.0.3", - "@types/node": "14.17.32", + "@types/node": "14.17.34", "@types/node-emoji": "1.8.1", - "@types/parse-link-header": "1.0.0", + "@types/parse-link-header": "1.0.1", "@types/registry-auth-token": "4.2.1", "@types/semver": "7.3.9", "@types/semver-stable": "3.0.0", @@ -240,49 +248,48 @@ "@types/traverse": "0.6.32", "@types/url-join": "4.0.1", "@types/xmldoc": "1.1.6", - "@typescript-eslint/eslint-plugin": "4.33.0", - "@typescript-eslint/parser": "4.33.0", + "@typescript-eslint/eslint-plugin": "5.5.0", + "@typescript-eslint/parser": "5.5.0", + "aws-sdk-client-mock": "0.5.6", "conventional-changelog-conventionalcommits": "4.6.1", "cross-env": "7.0.3", "emojibase-data": "6.2.0", - "eslint": "7.32.0", - "eslint-config-airbnb-typescript": "12.3.1", + "eslint": "8.3.0", "eslint-config-prettier": "8.3.0", "eslint-formatter-gha": "1.3.0", - "eslint-plugin-import": "2.25.2", - "eslint-plugin-jest": "24.7.0", + "eslint-plugin-import": "2.25.3", + "eslint-plugin-jest": "25.3.0", "eslint-plugin-promise": "5.1.1", "expect-more-jest": "5.4.0", "glob": "7.2.0", "graphql": "15.7.2", "husky": "7.0.4", - "jest": "27.3.1", - "jest-extended": "1.1.0", + "jest": "27.4.2", + "jest-extended": "1.2.0", "jest-github-actions-reporter": "1.0.3", "jest-junit": "13.0.0", "jest-mock-extended": "2.0.4", "jest-silent-reporter": "0.5.0", "markdownlint-cli2": "0.3.2", - "mock-fs": "5.1.1", + "mock-fs": "5.1.2", "mockdate": "3.0.5", - "nock": "13.1.4", + "nock": "13.2.1", "npm-run-all": "4.1.5", - "prettier": "2.4.1", - "pretty-quick": "3.1.1", + "prettier": "2.5.0", + "pretty-quick": "3.1.2", "rimraf": "3.0.2", - "semantic-release": "18.0.0", + "semantic-release": "18.0.1", "shelljs": "0.8.4", "strip-ansi": "6.0.1", "tmp-promise": "3.0.3", "ts-jest": "27.0.7", "ts-node": "10.4.0", - "type-fest": "2.5.2", - "typescript": "4.4.4", + "type-fest": "2.7.0", + "typescript": "4.5.2", "unified": "9.2.2" }, "resolutions": { - "**/css-what": "^5.0.1", - "**/kind-of": ">=6.0.3" + "**/json-schema": "^0.4.0" }, "files": [ "dist" diff --git a/readme.md b/readme.md index ff935b4e21ddd6..0b1c0a45475a11 100644 --- a/readme.md +++ b/readme.md @@ -63,7 +63,7 @@ If you are not on github.com or gitlab.com, or you prefer to run your own instan - Install the `renovate` CLI tool from npmjs, run it on a schedule (e.g. using cron) - Run the `renovate/renovate` Docker Hub image (same content/versions as the CLI tool), run it on a schedule -- Run the `renovate/renovate:slim` Docker Hub image if you only use package managers that don't need third party binaries (e.g. JS, Docker, NuGet, pip) +- Run the `renovate/renovate:slim` Docker Hub image if you only use package managers that don't need third-party binaries (e.g. JavaScript, Docker, NuGet, pip) [More details on the self-hosting development](https://github.com/renovatebot/renovate/blob/main/docs/usage/getting-started/running.md). diff --git a/renovate.json b/renovate.json index c878eaa505fcca..ad17d906890243 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,5 @@ { + "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["github>renovatebot/.github"], "assignees": ["rarkins", "viceice"], "semanticCommitScope": "deps", diff --git a/test/exec-util.ts b/test/exec-util.ts index 7e4d4e14972a4f..3f6a61d1700931 100644 --- a/test/exec-util.ts +++ b/test/exec-util.ts @@ -2,7 +2,7 @@ import { exec as _exec } from 'child_process'; import is from '@sindresorhus/is'; import traverse from 'traverse'; import { toUnix } from 'upath'; -import { ExecOptions } from '../lib/util/exec'; +import type { ExecOptions } from '../lib/util/exec/types'; import { regEx } from '../lib/util/regex'; type CallOptions = ExecOptions | null | undefined; @@ -27,7 +27,6 @@ export function execSnapshot(cmd: string, options?: CallOptions): ExecSnapshot { const cwd = toUnix(process.cwd()); - // eslint-disable-next-line array-callback-return return traverse(snapshot).map(function fixup(v) { if (is.string(v)) { const val = v diff --git a/test/graphql-snapshot.ts b/test/graphql-snapshot.ts index 66e7f9f3eb378d..75478315b40f92 100644 --- a/test/graphql-snapshot.ts +++ b/test/graphql-snapshot.ts @@ -37,11 +37,13 @@ type Variables = Record; interface SelectionSet { __vars?: Variables; __args?: Arguments; - [key: string]: null | SelectionSet | Arguments; + [key: string]: undefined | null | SelectionSet | Arguments; } interface GraphqlSnapshot { query?: SelectionSet; + mutation?: SelectionSet; + subscription?: SelectionSet; variables?: Record; } @@ -81,7 +83,7 @@ function getArguments(key: string, val: ValueNode): Arguments { } function simplifyArguments( - argNodes: ReadonlyArray + argNodes?: ReadonlyArray ): Arguments | null { if (argNodes) { let result: Arguments = {}; diff --git a/tools/check-fenced-code.mjs b/tools/check-fenced-code.mjs new file mode 100644 index 00000000000000..32fd71229712ef --- /dev/null +++ b/tools/check-fenced-code.mjs @@ -0,0 +1,65 @@ +import { promisify } from 'util'; +import fs from 'fs-extra'; +import g from 'glob'; +import MarkdownIt from 'markdown-it'; +import shell from 'shelljs'; + +const glob = promisify(g); + +const errorTitle = 'Invalid JSON in fenced code block'; +const errorBody = + 'Fix this manually by ensuring each block is a valid, complete JSON document.'; +const markdownGlob = '{docs,lib}/**/*.md'; +const markdown = new MarkdownIt('zero'); + +let issues = 0; + +markdown.enable(['fence']); + +function checkValidJson(file, token) { + const start = parseInt(token.map[0], 10) + 1; + const end = parseInt(token.map[1], 10) + 1; + + try { + JSON.parse(token.content); + } catch (err) { + issues += 1; + if (process.env.CI) { + shell.echo( + `::error file=${file},line=${start},endLine=${end},title=${errorTitle}::${err.message}. ${errorBody}` + ); + } else { + shell.echo( + `${errorTitle} (${file} lines ${start}-${end}): ${err.message}` + ); + } + } +} + +async function processFile(file) { + const text = await fs.readFile(file, 'utf8'); + const tokens = markdown.parse(text, undefined); + shell.echo(`Linting ${file}...`); + + tokens.forEach((token) => { + if (token.type === 'fence' && token.info === 'json') { + checkValidJson(file, token); + } + }); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +(async () => { + const files = await glob(markdownGlob); + + for (const file of files) { + await processFile(file); + } + + if (issues) { + shell.echo( + `${issues} issues found. ${errorBody} See above for lines affected.` + ); + shell.exit(1); + } +})(); diff --git a/tools/check-re2.mjs b/tools/check-re2.mjs index 04a90c4c3a2a95..0d55da98a28bd4 100644 --- a/tools/check-re2.mjs +++ b/tools/check-re2.mjs @@ -4,7 +4,7 @@ import shell from 'shelljs'; (async () => { shell.echo('-n', 'Checking re2 ... '); try { - const { default: RE2 } = await import('re2'); // eslint-disable-line import/no-extraneous-dependencies + const { default: RE2 } = await import('re2'); new RE2('.*').exec('test'); shell.echo(`ok.`); } catch (e) { diff --git a/tools/utils.mjs b/tools/utils.mjs index 34e0d88af752c2..2a3047276735fa 100644 --- a/tools/utils.mjs +++ b/tools/utils.mjs @@ -22,7 +22,7 @@ export { program }; */ export function exec(cmd) { try { - if (!program.dryRun) { + if (!options.dryRun) { const res = shell.exec(cmd); return res.code === 0; } diff --git a/tsconfig.app.json b/tsconfig.app.json index c486d2866203ed..4442a4f91da7ef 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -6,6 +6,7 @@ "noImplicitAny": false, "sourceMap": true, "inlineSources": true, + "importHelpers": true, "allowJs": false, "checkJs": false, "types": ["node"] diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 00000000000000..101c5a0ad2f8fb --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "allowJs": false, + "checkJs": false + } +} diff --git a/tsconfig.strict.json b/tsconfig.strict.json new file mode 100644 index 00000000000000..d2bb5af542a5e3 --- /dev/null +++ b/tsconfig.strict.json @@ -0,0 +1,73 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "strictNullChecks": true, + "noImplicitAny": true, + "lib": ["es2019"] + }, + "include": [ + "lib/config/app-strings.ts", + "lib/config/global.ts", + "lib/config/migrations/types.ts", + "lib/config/presets/common.ts", + "lib/constants/**/*.ts", + "lib/data-files.generated.ts", + "lib/datasource/**/common.ts", + "lib/datasource/**/types.ts", + "lib/globals.d.ts", + "lib/logger/**/*.ts", + "lib/manager/**/common.ts", + "lib/manager/**/types.ts", + "lib/platform/**/types.ts", + "lib/platform/github/graphql.ts", + "lib/platform/utils/pr-body.ts", + "lib/proxy.ts", + "lib/types/**/*.ts", + "lib/util/cache/*.ts", + "lib/util/clone.ts", + "lib/util/date.ts", + "lib/util/exec/common.ts", + "lib/util/exec/types.ts", + "lib/util/fs", + "lib/util/git/config.ts", + "lib/util/git/types.ts", + "lib/util/host-rules.ts", + "lib/util/html.ts", + "lib/util/http/hooks.ts", + "lib/util/http/types.ts", + "lib/util/index.ts", + "lib/util/json-writer/indentation-type.ts", + "lib/util/mask.spec.ts", + "lib/util/mask.ts", + "lib/util/object.ts", + "lib/util/regex.spec.ts", + "lib/util/regex.ts", + "lib/util/sanitize.ts", + "lib/util/split.ts", + "lib/util/url.ts", + "lib/versioning/swift/*.ts", + "lib/versioning/ubuntu/*.ts", + "lib/workers/pr/changelog/hbs-template.ts", + "lib/workers/pr/changelog/types.ts", + "lib/workers/repository/init/types.ts", + "lib/workers/repository/model/commit-message.ts", + "test/graphql-snapshot.ts", + "test/http-mock.ts", + "test/json-schema.ts", + "test/newline-snapshot-serializer.ts", + "test/static-files.spec.ts", + "tools/utils/index.ts" + ], + "exclude": [ + "lib/constants/platform.spec.ts", + "lib/datasource/docker/common.ts", + "lib/datasource/github-releases/common.ts", + "lib/datasource/go/common.ts", + "lib/datasource/go/types.ts", + "lib/datasource/helm/common.ts", + "lib/logger/err-serializer.spec.ts", + "lib/manager/gradle/shallow/types.ts", + "lib/util/cache/*.spec.ts", + "lib/util/fs/index.spec.ts" + ] +} diff --git a/yarn.lock b/yarn.lock index e27199c45abd43..c1f3a3d2a880fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,12 +17,21 @@ tunnel "0.0.6" "@arcanis/slice-ansi@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz#35331e41a1062e3c53c01ad2ec1555c5c1959d8f" - integrity sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.1.1.tgz#0ee328a68996ca45854450033a3d161421dc4f55" + integrity sha512-xguP2WR2Dv0gQ7Ykbdb7BNCnPnIPB94uTi0Z2NvkRBEnhbwjOQ7QyQKJXrVQg4qDpiD9hA5l5cCwy/z2OXgc3w== dependencies: grapheme-splitter "^1.0.4" +"@aws-crypto/crc32@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-2.0.0.tgz#4ad432a3c03ec3087c5540ff6e41e6565d2dc153" + integrity sha512-TvE1r2CUueyXOuHdEigYjIZVesInd9KN+K/TFFNfkkxRThiNxO6i4ZqqAVMoEjAamZZ1AA8WXJkjCz7YShHPQA== + dependencies: + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + tslib "^1.11.1" + "@aws-crypto/ie11-detection@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-1.0.0.tgz#d3a6af29ba7f15458f79c41d1cd8cac3925e726a" @@ -30,6 +39,27 @@ dependencies: tslib "^1.11.1" +"@aws-crypto/ie11-detection@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-2.0.0.tgz#bb6c2facf8f03457e949dcf0921477397ffa4c6e" + integrity sha512-pkVXf/dq6PITJ0jzYZ69VhL8VFOFoPZLZqtU/12SGnzYuJOOGNfF41q9GxdI1yqC8R13Rq3jOLKDFpUJFT5eTA== + dependencies: + tslib "^1.11.1" + +"@aws-crypto/sha256-browser@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz#741c9024df55ec59b51e5b1f5d806a4852699fb5" + integrity sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A== + dependencies: + "@aws-crypto/ie11-detection" "^2.0.0" + "@aws-crypto/sha256-js" "^2.0.0" + "@aws-crypto/supports-web-crypto" "^2.0.0" + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@aws-sdk/util-utf8-browser" "^3.0.0" + tslib "^1.11.1" + "@aws-crypto/sha256-browser@^1.0.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-1.2.2.tgz#004d806e3bbae130046c259ec3279a02d4a0b576" @@ -43,6 +73,15 @@ "@aws-sdk/util-locate-window" "^3.0.0" tslib "^1.11.1" +"@aws-crypto/sha256-js@2.0.0", "@aws-crypto/sha256-js@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz#f1f936039bdebd0b9e2dd834d65afdc2aac4efcb" + integrity sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig== + dependencies: + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + tslib "^1.11.1" + "@aws-crypto/sha256-js@^1.0.0", "@aws-crypto/sha256-js@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz#02acd1a1fda92896fc5a28ec7c6e164644ea32fc" @@ -59,6 +98,13 @@ dependencies: tslib "^1.11.1" +"@aws-crypto/supports-web-crypto@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.0.tgz#fd6cde30b88f77d5a4f57b2c37c560d918014f9e" + integrity sha512-Ge7WQ3E0OC7FHYprsZV3h0QIcpdyJLvIeg+uTuHqRYm8D6qCFJoiC+edSzSyFiHtZf+NOQDJ1q46qxjtzIY2nA== + dependencies: + tslib "^1.11.1" + "@aws-crypto/util@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-1.2.2.tgz#b28f7897730eb6538b21c18bd4de22d0ea09003c" @@ -68,6 +114,23 @@ "@aws-sdk/util-utf8-browser" "^3.0.0" tslib "^1.11.1" +"@aws-crypto/util@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-2.0.0.tgz#17ba6f83c7e447b70fc24b84c5f6714d1e329f4a" + integrity sha512-YDooyH83m2P5A3h6lNH7hm6mIP93sU/dtzRmXIgtO4BCB7SvtX8ysVKQAE8tVky2DQ3HHxPCjNTuUe7YoAMrNQ== + dependencies: + "@aws-sdk/types" "^3.1.0" + "@aws-sdk/util-utf8-browser" "^3.0.0" + tslib "^1.11.1" + +"@aws-sdk/abort-controller@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.35.0.tgz#66665a2bc80ea9db9901bf4c6ca4cac06812713a" + integrity sha512-88DAWQNT3IMGvCQkTCaOngJ7F6czoDDVbEmJ3vRvWa4B1Gve3U/pKIw26eJJsiR5BlihDRksjPJ6oqKlBj/QYQ== + dependencies: + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/abort-controller@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.38.0.tgz#c59d6c1317e96951e1304e7fb15e6b01ed2f9fc8" @@ -76,6 +139,71 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/abort-controller@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.40.0.tgz#e17299776782483835439d9b1b5300add24adc3f" + integrity sha512-S7LzLvNuwuf0q7r4q7zqGzxd/W2xYsn8cpZ90MMb3ObolhbkLySrikUJujmXae8k+2/KFCOr+FVC0YLrATSUgQ== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/chunked-blob-reader-native@3.37.0": + version "3.37.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader-native/-/chunked-blob-reader-native-3.37.0.tgz#4dcb3624246377079f8626de698b8f05552b9dca" + integrity sha512-h9OYq6EvDrpb7SKod+Kow+d3aRNFVBYR1a8G8ahEDDQe3AtmA2Smyvni4kt/ABTiKvYdof2//Pq3BL/IUV9n9Q== + dependencies: + "@aws-sdk/util-base64-browser" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/chunked-blob-reader@3.37.0": + version "3.37.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.37.0.tgz#8549adddb89dfecfedab53571c0b92a9951bbbcf" + integrity sha512-uDacnFaczeO962RnVttwAQddS4rgDfI7nfeY8NV6iZkDv5uxGzHTfH4jT7WvPDM1pSMcOMDx8RJ+Tmtsd1VTsA== + dependencies: + tslib "^2.3.0" + +"@aws-sdk/client-ec2@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-ec2/-/client-ec2-3.35.0.tgz#2468830c63d5d2b451a7c6f63e7d0cb92f28ec0c" + integrity sha512-jY6Fv/sT0AfCN5PjDasEoxGf/+jdFoK06AtY0dL3YdHRuqn/1Y4xfCHtCC9KKmS7wiCno2vqCQp/QMHHATdctA== + dependencies: + "@aws-crypto/sha256-browser" "^1.0.0" + "@aws-crypto/sha256-js" "^1.0.0" + "@aws-sdk/client-sts" "3.35.0" + "@aws-sdk/config-resolver" "3.35.0" + "@aws-sdk/credential-provider-node" "3.35.0" + "@aws-sdk/fetch-http-handler" "3.35.0" + "@aws-sdk/hash-node" "3.35.0" + "@aws-sdk/invalid-dependency" "3.35.0" + "@aws-sdk/middleware-content-length" "3.35.0" + "@aws-sdk/middleware-host-header" "3.35.0" + "@aws-sdk/middleware-logger" "3.35.0" + "@aws-sdk/middleware-retry" "3.35.0" + "@aws-sdk/middleware-sdk-ec2" "3.35.0" + "@aws-sdk/middleware-serde" "3.35.0" + "@aws-sdk/middleware-signing" "3.35.0" + "@aws-sdk/middleware-stack" "3.35.0" + "@aws-sdk/middleware-user-agent" "3.35.0" + "@aws-sdk/node-config-provider" "3.35.0" + "@aws-sdk/node-http-handler" "3.35.0" + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/smithy-client" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/url-parser" "3.35.0" + "@aws-sdk/util-base64-browser" "3.35.0" + "@aws-sdk/util-base64-node" "3.35.0" + "@aws-sdk/util-body-length-browser" "3.35.0" + "@aws-sdk/util-body-length-node" "3.35.0" + "@aws-sdk/util-user-agent-browser" "3.35.0" + "@aws-sdk/util-user-agent-node" "3.35.0" + "@aws-sdk/util-utf8-browser" "3.35.0" + "@aws-sdk/util-utf8-node" "3.35.0" + "@aws-sdk/util-waiter" "3.35.0" + entities "2.2.0" + fast-xml-parser "3.19.0" + tslib "^2.3.0" + uuid "^8.3.2" + "@aws-sdk/client-ecr@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-ecr/-/client-ecr-3.38.0.tgz#30006eb61a6487138f09ab8ab758d64248d93aa6" @@ -114,6 +242,93 @@ "@aws-sdk/util-waiter" "3.38.0" tslib "^2.3.0" +"@aws-sdk/client-s3@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.41.0.tgz#6453162aee5ecc8534baa6045f3a368f5a6c06da" + integrity sha512-PGPy7MbyjkpQECPR02NOxucqJWJEah9IcQcUMyRB1vZBg6irBI1d4GTuWafegr3xKQ+7ZTraupDtUSJ9oRqKOg== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/client-sts" "3.41.0" + "@aws-sdk/config-resolver" "3.40.0" + "@aws-sdk/credential-provider-node" "3.41.0" + "@aws-sdk/eventstream-serde-browser" "3.40.0" + "@aws-sdk/eventstream-serde-config-resolver" "3.40.0" + "@aws-sdk/eventstream-serde-node" "3.40.0" + "@aws-sdk/fetch-http-handler" "3.40.0" + "@aws-sdk/hash-blob-browser" "3.40.0" + "@aws-sdk/hash-node" "3.40.0" + "@aws-sdk/hash-stream-node" "3.40.0" + "@aws-sdk/invalid-dependency" "3.40.0" + "@aws-sdk/md5-js" "3.40.0" + "@aws-sdk/middleware-apply-body-checksum" "3.40.0" + "@aws-sdk/middleware-bucket-endpoint" "3.41.0" + "@aws-sdk/middleware-content-length" "3.40.0" + "@aws-sdk/middleware-expect-continue" "3.40.0" + "@aws-sdk/middleware-host-header" "3.40.0" + "@aws-sdk/middleware-location-constraint" "3.40.0" + "@aws-sdk/middleware-logger" "3.40.0" + "@aws-sdk/middleware-retry" "3.40.0" + "@aws-sdk/middleware-sdk-s3" "3.41.0" + "@aws-sdk/middleware-serde" "3.40.0" + "@aws-sdk/middleware-signing" "3.40.0" + "@aws-sdk/middleware-ssec" "3.40.0" + "@aws-sdk/middleware-stack" "3.40.0" + "@aws-sdk/middleware-user-agent" "3.40.0" + "@aws-sdk/node-config-provider" "3.40.0" + "@aws-sdk/node-http-handler" "3.40.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/smithy-client" "3.41.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/url-parser" "3.40.0" + "@aws-sdk/util-base64-browser" "3.37.0" + "@aws-sdk/util-base64-node" "3.37.0" + "@aws-sdk/util-body-length-browser" "3.37.0" + "@aws-sdk/util-body-length-node" "3.37.0" + "@aws-sdk/util-user-agent-browser" "3.40.0" + "@aws-sdk/util-user-agent-node" "3.40.0" + "@aws-sdk/util-utf8-browser" "3.37.0" + "@aws-sdk/util-utf8-node" "3.37.0" + "@aws-sdk/util-waiter" "3.40.0" + "@aws-sdk/xml-builder" "3.37.0" + entities "2.2.0" + fast-xml-parser "3.19.0" + tslib "^2.3.0" + +"@aws-sdk/client-sso@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.35.0.tgz#619afaec5130dcaf76d2ce0f67897f19858d6cff" + integrity sha512-hAIgl/lgvUWgCZauAPH/tNbk+nhFLPaemwhjmk5rjRjzeF3Vw4e1FCEoJdZTg7lyV7mcUkJtim5gRf2k0Vy4rw== + dependencies: + "@aws-crypto/sha256-browser" "^1.0.0" + "@aws-crypto/sha256-js" "^1.0.0" + "@aws-sdk/config-resolver" "3.35.0" + "@aws-sdk/fetch-http-handler" "3.35.0" + "@aws-sdk/hash-node" "3.35.0" + "@aws-sdk/invalid-dependency" "3.35.0" + "@aws-sdk/middleware-content-length" "3.35.0" + "@aws-sdk/middleware-host-header" "3.35.0" + "@aws-sdk/middleware-logger" "3.35.0" + "@aws-sdk/middleware-retry" "3.35.0" + "@aws-sdk/middleware-serde" "3.35.0" + "@aws-sdk/middleware-stack" "3.35.0" + "@aws-sdk/middleware-user-agent" "3.35.0" + "@aws-sdk/node-config-provider" "3.35.0" + "@aws-sdk/node-http-handler" "3.35.0" + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/smithy-client" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/url-parser" "3.35.0" + "@aws-sdk/util-base64-browser" "3.35.0" + "@aws-sdk/util-base64-node" "3.35.0" + "@aws-sdk/util-body-length-browser" "3.35.0" + "@aws-sdk/util-body-length-node" "3.35.0" + "@aws-sdk/util-user-agent-browser" "3.35.0" + "@aws-sdk/util-user-agent-node" "3.35.0" + "@aws-sdk/util-utf8-browser" "3.35.0" + "@aws-sdk/util-utf8-node" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/client-sso@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.38.0.tgz#9613b614485f296fb6be1da29f16c74e1ba3904b" @@ -148,6 +363,79 @@ "@aws-sdk/util-utf8-node" "3.37.0" tslib "^2.3.0" +"@aws-sdk/client-sso@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.41.0.tgz#33d49e926ef6fff08278b256454241f1f982d8de" + integrity sha512-xDvcy7wv3KdHhOpl5fZN+Ydw+dHBmsCZwMFI1ZdJVCSGO+ZKgl5KVWi1LCif6vjZP1pUuGl44oDOZz1ACqOzTg== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.40.0" + "@aws-sdk/fetch-http-handler" "3.40.0" + "@aws-sdk/hash-node" "3.40.0" + "@aws-sdk/invalid-dependency" "3.40.0" + "@aws-sdk/middleware-content-length" "3.40.0" + "@aws-sdk/middleware-host-header" "3.40.0" + "@aws-sdk/middleware-logger" "3.40.0" + "@aws-sdk/middleware-retry" "3.40.0" + "@aws-sdk/middleware-serde" "3.40.0" + "@aws-sdk/middleware-stack" "3.40.0" + "@aws-sdk/middleware-user-agent" "3.40.0" + "@aws-sdk/node-config-provider" "3.40.0" + "@aws-sdk/node-http-handler" "3.40.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/smithy-client" "3.41.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/url-parser" "3.40.0" + "@aws-sdk/util-base64-browser" "3.37.0" + "@aws-sdk/util-base64-node" "3.37.0" + "@aws-sdk/util-body-length-browser" "3.37.0" + "@aws-sdk/util-body-length-node" "3.37.0" + "@aws-sdk/util-user-agent-browser" "3.40.0" + "@aws-sdk/util-user-agent-node" "3.40.0" + "@aws-sdk/util-utf8-browser" "3.37.0" + "@aws-sdk/util-utf8-node" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/client-sts@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.35.0.tgz#2a758b02c5d7f6d716b382fa169a16bb38ffd7a9" + integrity sha512-ciMy34LS50UO3U8eim8u8DqBj3bQeR3ULbq6YvoWeGZzPCIvshnvyNesk7QF7yvwxRn2+TDY7EZdy2wq+xQnQA== + dependencies: + "@aws-crypto/sha256-browser" "^1.0.0" + "@aws-crypto/sha256-js" "^1.0.0" + "@aws-sdk/config-resolver" "3.35.0" + "@aws-sdk/credential-provider-node" "3.35.0" + "@aws-sdk/fetch-http-handler" "3.35.0" + "@aws-sdk/hash-node" "3.35.0" + "@aws-sdk/invalid-dependency" "3.35.0" + "@aws-sdk/middleware-content-length" "3.35.0" + "@aws-sdk/middleware-host-header" "3.35.0" + "@aws-sdk/middleware-logger" "3.35.0" + "@aws-sdk/middleware-retry" "3.35.0" + "@aws-sdk/middleware-sdk-sts" "3.35.0" + "@aws-sdk/middleware-serde" "3.35.0" + "@aws-sdk/middleware-signing" "3.35.0" + "@aws-sdk/middleware-stack" "3.35.0" + "@aws-sdk/middleware-user-agent" "3.35.0" + "@aws-sdk/node-config-provider" "3.35.0" + "@aws-sdk/node-http-handler" "3.35.0" + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/smithy-client" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/url-parser" "3.35.0" + "@aws-sdk/util-base64-browser" "3.35.0" + "@aws-sdk/util-base64-node" "3.35.0" + "@aws-sdk/util-body-length-browser" "3.35.0" + "@aws-sdk/util-body-length-node" "3.35.0" + "@aws-sdk/util-user-agent-browser" "3.35.0" + "@aws-sdk/util-user-agent-node" "3.35.0" + "@aws-sdk/util-utf8-browser" "3.35.0" + "@aws-sdk/util-utf8-node" "3.35.0" + entities "2.2.0" + fast-xml-parser "3.19.0" + tslib "^2.3.0" + "@aws-sdk/client-sts@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.38.0.tgz#f09b324d40bff9af8bc80e1c486eb3747473a1be" @@ -187,6 +475,54 @@ fast-xml-parser "3.19.0" tslib "^2.3.0" +"@aws-sdk/client-sts@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.41.0.tgz#38bde53d0cd1254894d0b27b4cc3f056f5d2692e" + integrity sha512-XTjmr53kMbXuVhH3B+g2jEYuhNralptsMSd4RcSHCB7BX1NmAMnMFKKTmVlmc5NizWi4x1CzExu86Q0YSqp0og== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.40.0" + "@aws-sdk/credential-provider-node" "3.41.0" + "@aws-sdk/fetch-http-handler" "3.40.0" + "@aws-sdk/hash-node" "3.40.0" + "@aws-sdk/invalid-dependency" "3.40.0" + "@aws-sdk/middleware-content-length" "3.40.0" + "@aws-sdk/middleware-host-header" "3.40.0" + "@aws-sdk/middleware-logger" "3.40.0" + "@aws-sdk/middleware-retry" "3.40.0" + "@aws-sdk/middleware-sdk-sts" "3.40.0" + "@aws-sdk/middleware-serde" "3.40.0" + "@aws-sdk/middleware-signing" "3.40.0" + "@aws-sdk/middleware-stack" "3.40.0" + "@aws-sdk/middleware-user-agent" "3.40.0" + "@aws-sdk/node-config-provider" "3.40.0" + "@aws-sdk/node-http-handler" "3.40.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/smithy-client" "3.41.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/url-parser" "3.40.0" + "@aws-sdk/util-base64-browser" "3.37.0" + "@aws-sdk/util-base64-node" "3.37.0" + "@aws-sdk/util-body-length-browser" "3.37.0" + "@aws-sdk/util-body-length-node" "3.37.0" + "@aws-sdk/util-user-agent-browser" "3.40.0" + "@aws-sdk/util-user-agent-node" "3.40.0" + "@aws-sdk/util-utf8-browser" "3.37.0" + "@aws-sdk/util-utf8-node" "3.37.0" + entities "2.2.0" + fast-xml-parser "3.19.0" + tslib "^2.3.0" + +"@aws-sdk/config-resolver@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.35.0.tgz#e8c746d04dc14099df6a05a6e23ec662480c0e87" + integrity sha512-r3s2VDSAJcveQuICdNSzRzsNrAPGGISUDb+0rqYIZVY7FWWWDGghlBqIrAyY6BFvjCn+LTpqF225+6eUxi1AWg== + dependencies: + "@aws-sdk/signature-v4" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/config-resolver@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.38.0.tgz#c630e0efaee480ceac1cd3a3fba50b26ccc7fd31" @@ -196,6 +532,25 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/config-resolver@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.40.0.tgz#d7bd3180aebced797800661a2ed778a5db8ac7e5" + integrity sha512-QYy6J2k31QL6J74hPBfptnLW1kQYdN+xjwH4UQ1mv7EUhRoJN9ZY2soStJowFy4at6IIOOVWbyG5dyqvrbEovg== + dependencies: + "@aws-sdk/signature-v4" "3.40.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-config-provider" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/credential-provider-env@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.35.0.tgz#3dc3b06d99cee082ad62558a8635c3b5389777b9" + integrity sha512-NC87G4fh5AintHrKe2Bjw/5l4VB6EOVV0vrX3fWYDybHJQnxLDDrw98z5cH6khIGp0PpaFoWGMWgEunWADo5Rg== + dependencies: + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/credential-provider-env@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.38.0.tgz#994ed65fdc66ea2c9a5d640c402d158b9ffa0158" @@ -205,6 +560,26 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/credential-provider-env@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.40.0.tgz#0ca7611f13520dd6654e8eac7fa3e767d027ede6" + integrity sha512-qHZdf2vxhzZkSygjw2I4SEYFL2dMZxxYvO4QlkqQouKY81OVxs/j69oiNCjPasQzGz5jaZZKI8xEAIfkSyr1lg== + dependencies: + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/credential-provider-imds@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.35.0.tgz#8cba5a8dc1006bf92cd8904b17d4caad4718908c" + integrity sha512-AcgTpeyE0Br0F1YRZaJMGTXjQi+7pEosKMXLknCyIDtGGLp+NI/CFKEPnS9arEkK2bWeGWuXhbTbPFz0iMXq7A== + dependencies: + "@aws-sdk/node-config-provider" "3.35.0" + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/url-parser" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/credential-provider-imds@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.38.0.tgz#b32d343b4fb252d682fe9f7b591dc28c6c01ec04" @@ -216,6 +591,32 @@ "@aws-sdk/url-parser" "3.38.0" tslib "^2.3.0" +"@aws-sdk/credential-provider-imds@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.40.0.tgz#7c324eff731f85d4d40763c484e78673aa5dedfb" + integrity sha512-Ty/wVa+BQrCFrP06AGl5S1CeLifDt68YrlYXUnkRn603SX4DvxBgVO7XFeDH58G8ziDCiqxfmVl4yjbncPPeSw== + dependencies: + "@aws-sdk/node-config-provider" "3.40.0" + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/url-parser" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/credential-provider-ini@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.35.0.tgz#9ef6f9ccc022724dc21e6523c11854554bb773a8" + integrity sha512-gOzAeC711wx9bNzJMuaEMxvG38STFXB2kxjQpEdIOQO+2ywhhL2IITCQhe5+Ij3BYTi5mOtUq+LYMNBeX0yxmw== + dependencies: + "@aws-sdk/credential-provider-env" "3.35.0" + "@aws-sdk/credential-provider-imds" "3.35.0" + "@aws-sdk/credential-provider-sso" "3.35.0" + "@aws-sdk/credential-provider-web-identity" "3.35.0" + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/shared-ini-file-loader" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-credentials" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/credential-provider-ini@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.38.0.tgz#4e8a9c6e90ff9746203470b76ede9961242789b7" @@ -231,6 +632,38 @@ "@aws-sdk/util-credentials" "3.37.0" tslib "^2.3.0" +"@aws-sdk/credential-provider-ini@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.41.0.tgz#a212444f6e4d03c0683ed1b6479bca72eab782dd" + integrity sha512-98CGEHg7Tb6HxK5ZIdbAcijvD3IpLe0ddse1xMe/Ilhjz770FS/L2UNprOP6PZTqrSfBffiMrvfThUSuUaTlIQ== + dependencies: + "@aws-sdk/credential-provider-env" "3.40.0" + "@aws-sdk/credential-provider-imds" "3.40.0" + "@aws-sdk/credential-provider-sso" "3.41.0" + "@aws-sdk/credential-provider-web-identity" "3.41.0" + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/shared-ini-file-loader" "3.37.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-credentials" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/credential-provider-node@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.35.0.tgz#2caec3047e8d2c7c2d81331b18f9d93df5f10d34" + integrity sha512-WnR6IdBeVlrHu8GPGjQii3O9JA50rTci3AyJ+bybpKT7joNDRKXSTO2itVkHaHiS3v66nuo/Hdcz4LwHAz3egg== + dependencies: + "@aws-sdk/credential-provider-env" "3.35.0" + "@aws-sdk/credential-provider-imds" "3.35.0" + "@aws-sdk/credential-provider-ini" "3.35.0" + "@aws-sdk/credential-provider-process" "3.35.0" + "@aws-sdk/credential-provider-sso" "3.35.0" + "@aws-sdk/credential-provider-web-identity" "3.35.0" + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/shared-ini-file-loader" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-credentials" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/credential-provider-node@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.38.0.tgz#14a3618014b7177c0697f507994dc9c6182f91dd" @@ -248,6 +681,34 @@ "@aws-sdk/util-credentials" "3.37.0" tslib "^2.3.0" +"@aws-sdk/credential-provider-node@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.41.0.tgz#ab4fc10ea6c7a2b42c903f4bdb68fea8ada5f5dd" + integrity sha512-5FW6+wNJgyDCsbAd+mLm/1DBTDkyIYOMVzcxbr6Vi3pM4UrMFdeLdAP62edYW8usg78Xg+c6vaAoEv/M3zkS0Q== + dependencies: + "@aws-sdk/credential-provider-env" "3.40.0" + "@aws-sdk/credential-provider-imds" "3.40.0" + "@aws-sdk/credential-provider-ini" "3.41.0" + "@aws-sdk/credential-provider-process" "3.40.0" + "@aws-sdk/credential-provider-sso" "3.41.0" + "@aws-sdk/credential-provider-web-identity" "3.41.0" + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/shared-ini-file-loader" "3.37.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-credentials" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/credential-provider-process@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.35.0.tgz#56eca98cb399c2ac2dd6dfd77123ae7425c5362f" + integrity sha512-oMxd7ONlI2a2l5wlgk2GzvoGybA8LKWhhrqiAUgQk2oN7rlq+elvUF95RWS4tiFCoZyGC9cXu9fFD/TN4ppETA== + dependencies: + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/shared-ini-file-loader" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-credentials" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/credential-provider-process@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.38.0.tgz#432eac9814d3de3090764f933aebb498f4608727" @@ -259,6 +720,29 @@ "@aws-sdk/util-credentials" "3.37.0" tslib "^2.3.0" +"@aws-sdk/credential-provider-process@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.40.0.tgz#b4f16e43ca9c855002e833ac9dc8e409b3c7ca23" + integrity sha512-qsaNCDesW2GasDbzpeOA371gxugi05JWxt3EKonLbUfkGKBK7kmmL6EgLIxZuNm2/Ve4RS07PKp8yBGm4xIx9w== + dependencies: + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/shared-ini-file-loader" "3.37.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-credentials" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/credential-provider-sso@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.35.0.tgz#9da1831818a57abae5579364d608e88ca434d921" + integrity sha512-3FYZcQMHfEtYlwZSEzAlnMrMnQby4FRgsIXxM/EP1Q6vxaVturZbT5vd1eytgPLGgKUHlxnHP5fpnsybAqTijQ== + dependencies: + "@aws-sdk/client-sso" "3.35.0" + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/shared-ini-file-loader" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-credentials" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/credential-provider-sso@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.38.0.tgz#cabc3f761a1141935d7fe1b67e2ef5975b60cbc2" @@ -271,6 +755,27 @@ "@aws-sdk/util-credentials" "3.37.0" tslib "^2.3.0" +"@aws-sdk/credential-provider-sso@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.41.0.tgz#66c83a776ec42f08b4ea6d619351f0240d57f76a" + integrity sha512-9s7SWu3RVIQ/MTcBCt35EMzxNQm3avivrbpSOKfJwxR5L+oNKPsV+gSqMlkNZGwOVJyUicIsZGcq/4ON6CjrOg== + dependencies: + "@aws-sdk/client-sso" "3.41.0" + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/shared-ini-file-loader" "3.37.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-credentials" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/credential-provider-web-identity@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.35.0.tgz#d3248e850badd9d97a47fe873735ce64365b1871" + integrity sha512-2I+pcIW76eqgRot8jELyZEG0wTDMHna3NwQ/QgiNTkGVTjhrbtVxzrDSEwZZ0vN0p2aOjW3IOCPPkDSm9kK7fw== + dependencies: + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/credential-provider-web-identity@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.38.0.tgz#1906b514fe361f7c36debc34cffaeb5d2c8712ec" @@ -280,6 +785,73 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/credential-provider-web-identity@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.41.0.tgz#7f0e9cc5650eaf6ac32ef359fb0e0dea2ca0ce78" + integrity sha512-VqvVoEh9C8xTXl4stKyJC5IKQhS8g1Gi5k6B9HPHLIxFRRfKxkE73DT4pMN6npnus7o0yi0MTFGQFQGYSrFO2g== + dependencies: + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/eventstream-marshaller@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-marshaller/-/eventstream-marshaller-3.40.0.tgz#c98fee85a751d2a914d1c4b66217d16d4ee4db03" + integrity sha512-zHGchfkG3B9M8OOKRpByeS5g1/15YQ0+QQHwxQRtm/CPtKBAIAsCZRQaCNBLu9uQMtBBKj5JsDUcjirhGeSvIg== + dependencies: + "@aws-crypto/crc32" "2.0.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-hex-encoding" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/eventstream-serde-browser@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.40.0.tgz#2b0f8c5ed1908a4fdab07d7b590bdd724b5a13a2" + integrity sha512-V0AXAfSkhY0hgxDJ0cNA+r42kL8295U7UTCp2Q2fvCaob3wKWh+54KZ2L4IOYTlK3yNzXJ5V6PP1zUuRlsUTew== + dependencies: + "@aws-sdk/eventstream-marshaller" "3.40.0" + "@aws-sdk/eventstream-serde-universal" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/eventstream-serde-config-resolver@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.40.0.tgz#4a6cced34cbdc376c95e174943b99cd352ed34b0" + integrity sha512-GgGiJBsQ1/SBTpRM/wCdFBCMo1Nybvy46bNVkH1ujCdp8UTLc5PozzNpH+15V2IQbc9sPDYffMab6HSFjDp5vw== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/eventstream-serde-node@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.40.0.tgz#715c943c9648a5d2b86341ae27eaae5c3bab9c0c" + integrity sha512-CnzX/JZGvhWlg+ooIPVZ78T+5wIm5Ld1BD7jwhlptJa8IjTMvkc8Nh4pAhc7T0ZScy4zZa/oTkqeVYCOVCyd1Q== + dependencies: + "@aws-sdk/eventstream-marshaller" "3.40.0" + "@aws-sdk/eventstream-serde-universal" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/eventstream-serde-universal@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.40.0.tgz#6d00ecd75d4910113e0f8f379b4cb6c30799dafe" + integrity sha512-rkHwVMyZJMhp9iBixkuaAGQNer/DPxZ9kxDDtE+LuAMhepTYQ8c4lUW0QQhYbNMWf48QKD1G4FV3JXIj9JfP9A== + dependencies: + "@aws-sdk/eventstream-marshaller" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/fetch-http-handler@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.35.0.tgz#e008fa1be8c8208bd6c24cd82da2e7da971d2ab2" + integrity sha512-tXbRtqC5kCxis65AIXsCZW9eWScC/gxm3+KyocNHzGgVgr8eyq/6hPmxCH8g3eHLixmVBR6xSKCnRT1q7frq2Q== + dependencies: + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/querystring-builder" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-base64-browser" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/fetch-http-handler@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.38.0.tgz#078ba2fcbacc5863ef5cb0ba4e7130e00c6ce5c5" @@ -291,6 +863,36 @@ "@aws-sdk/util-base64-browser" "3.37.0" tslib "^2.3.0" +"@aws-sdk/fetch-http-handler@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.40.0.tgz#5e6ecfb7fe1f32a5709e4e9c13b0536073477737" + integrity sha512-w1HiZromoU+/bbEo89uO81l6UO/M+c2uOMnXntZqe6t3ZHUUUo3AbvhKh0QGVFqRQa+Oi0+95KqWmTHa72/9Iw== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/querystring-builder" "3.40.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-base64-browser" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/hash-blob-browser@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.40.0.tgz#7bf071b84c59246736044499fd42255c6c8e3d46" + integrity sha512-l8xyprVVKKH+720VrQ677X6VkvHttDXB4MxkMuxhSvwYBQwsRzP2Wppo7xIAtWGoS+oqlLmD4LCbHdhFRcN5yA== + dependencies: + "@aws-sdk/chunked-blob-reader" "3.37.0" + "@aws-sdk/chunked-blob-reader-native" "3.37.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/hash-node@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.35.0.tgz#d0a160436b5bfb9d73f75f4c83cbd957169b98d1" + integrity sha512-Rx8V4d4XumcrmFNk/9eSti+z8xz94FklvddcfF4QMdKom4htdm7uU214VOnaqRW+WpxQAmiHmmuzAr3L1r7IGQ== + dependencies: + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-buffer-from" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/hash-node@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.38.0.tgz#8fe9d677d1d8df5b1b96a5e708370382e115920c" @@ -300,6 +902,31 @@ "@aws-sdk/util-buffer-from" "3.37.0" tslib "^2.3.0" +"@aws-sdk/hash-node@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.40.0.tgz#bf4d31a41652cbc3c937055087c80096cfab67ae" + integrity sha512-yOXXK85DdGDktdnQtXgMdaVKii4wtMjEhJ1mrvx2A9nMFNaPhxvERkVVIUKSWlJRa9ZujOw5jWOx8d2R51/Kjg== + dependencies: + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-buffer-from" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/hash-stream-node@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-stream-node/-/hash-stream-node-3.40.0.tgz#0a6fda20faa0abebf946c6dae63a80c1e707c011" + integrity sha512-4yvRwODMGYtj6qrt+fyydV5MwVwPPoyoeqDoXdLo9x75vRY71DT1pMRt8PDOoY/ZwWbIdEt4+V7x0sLt2uy9WA== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/invalid-dependency@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.35.0.tgz#7d8fae8f12795a48f908710d17526bf209b37482" + integrity sha512-utzICeE5ggu0UnUQFGyAoZL+x1Mowb74a4FJcQV+9Zjqb5Mx7hpJ/u1i4F2qj1KbZqj70qMYW3YdhSvZcMZiVw== + dependencies: + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/invalid-dependency@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.38.0.tgz#04d25d195edb25837f722a67489e67275c0f2a9e" @@ -308,6 +935,21 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/invalid-dependency@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.40.0.tgz#023e37abfb2882676c3cef02da630342634aa429" + integrity sha512-axIWtDwCBDDqEgAJipX1FB1ZNpWYXquVwKDMo+7G+ftPBZ4FEq4M1ELhXJL3hhNJ9ZmCQzv+4F6Wnt8dwuzUaQ== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/is-array-buffer@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/is-array-buffer/-/is-array-buffer-3.35.0.tgz#6edec4806231fd2dc9643a094507e15e079452c8" + integrity sha512-dsJU/U+oJuXWRyFmt/a+0s7AH8nUFrfhfGh+J8LkToIBQLTgmYdjxhemOYAZUKh7zGWwTlCTsQoUWf6eKOyrFw== + dependencies: + tslib "^2.3.0" + "@aws-sdk/is-array-buffer@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/is-array-buffer/-/is-array-buffer-3.37.0.tgz#aa87619f8172b1a2a7ac8d573032025d98ae6c50" @@ -315,6 +957,46 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/md5-js@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/md5-js/-/md5-js-3.40.0.tgz#027ff231c795a6069ae854ccf7c8b57dbfd441fe" + integrity sha512-P1tzEljMD/MkjSc00TkVBYvfaVv/7S+04YEwE7tpu/jtxWxMHnk3CMKqq/F2iMhY83DRoqoYy+YqnaF4Bzr1uA== + dependencies: + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-utf8-browser" "3.37.0" + "@aws-sdk/util-utf8-node" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-apply-body-checksum@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-apply-body-checksum/-/middleware-apply-body-checksum-3.40.0.tgz#ebf6cf2f5c4078a4ca717d67dc72b292abe36f33" + integrity sha512-gNSFlFu/O8cxAM0X64OwiLLN/NPXvK3FsAIJRsfhIW+dX0bEq4lsGPsdU8Tx+9eenaj/Z01uqgWZ6Izar8zVvQ== + dependencies: + "@aws-sdk/is-array-buffer" "3.37.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-bucket-endpoint@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.41.0.tgz#d1bb0fb59be1bb4176725c243561dd7897e710cb" + integrity sha512-4A0kWHH2qemd4P7CZKS7XB6qtSUP2xMJ7Dn/llxYgvadR0mKEfGPeYPhAss/k7T1JGv+kSTIV30RwUXwdXgE/A== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-arn-parser" "3.37.0" + "@aws-sdk/util-config-provider" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-content-length@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.35.0.tgz#2dadfcff5c21eac7876e373d48e91f7b91b03ea5" + integrity sha512-hkv8k/wM6XtX2fp13f45Vtzf1xOul4X7y8ksAylDystKJQ+Ks9krGlgaYgV8naP2MTgFxlB8wR44cGBK1pHuYw== + dependencies: + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/middleware-content-length@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.38.0.tgz#c5c80ce7c4bd9f7b8d2056f67b004c85ec4e9e90" @@ -324,6 +1006,43 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/middleware-content-length@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.40.0.tgz#affe235fc0eb43c7b8e21189f85a238fdd0b4c3f" + integrity sha512-sybAJb8v7I/vvL08R3+TI/XDAg9gybQTZ2treC24Ap4+jAOz4QBTHJPMKaUlEeFlMUcq4rj6/u2897ebYH6opw== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-expect-continue@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.40.0.tgz#380bb30c7c9d0b52bf044207b964d803e4cb5e3d" + integrity sha512-FY6vT0u1ptDZ2bBj1yG/Iyk6HZB7U9fbrpeZNPYzgq8HJxBcTgfLwtB3VLobyhThQm9X2a7R2YZrwtArW8yQfQ== + dependencies: + "@aws-sdk/middleware-header-default" "3.40.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-header-default@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-header-default/-/middleware-header-default-3.40.0.tgz#7e30d4e65404a7f914951b69703d54a425a0b3b0" + integrity sha512-eXQ13x/AivPZKoG8/akp9g5xdNHuKftl83GMuk9K6tt4+eAa22TdxiFu4R0UVlKAvo2feqxFrNs5DhhhBeAQWA== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-host-header@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.35.0.tgz#8e153c70da3677051101b794748674c13cc7efdd" + integrity sha512-lJTCq7tlDJpXcGDAmRl7Cs4iPbdYAub5FuOw017PpIFYaXolQP241FPX8NgJRlPqQH3qOjQ6uk2ylGk5mJP5xA== + dependencies: + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/middleware-host-header@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.38.0.tgz#c4ad8bc24ac4d48e88ec524b167c5e132daaa211" @@ -333,6 +1052,31 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/middleware-host-header@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.40.0.tgz#a6a1d52ab0da7f8e65a199c27d71750f8329eccc" + integrity sha512-/wocR7JFOLM7/+BQM1DgAd6KCFYcdxYu1P7AhI451GlVNuYa5f89zh7p0gt3SRC6monI5lXgpL7RudhDm8fTrA== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-location-constraint@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.40.0.tgz#f689b2860b9932beb143b6d83281ec2d4527d175" + integrity sha512-9XaVPYsDQVJbWJH96sNdv4HHY3j1raman+lYxMu4528Awp0OdWUeSsGRYRN+CnRPlkHnfNw4m6SKdWYHxdjshw== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-logger@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.35.0.tgz#d2be023d557d9cb77fe71ee6a6c125c7691605b2" + integrity sha512-d1QoKX38s8q1eCm15pgkp9SxdvgjaZpPaCfRLuXWN8hhlPA7/RhE6fa7mwaNG/ZAqSA7CB5FzFx+wkvmdgVFhw== + dependencies: + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/middleware-logger@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.38.0.tgz#3df66285b12c8fe3ab327c07afe6e1beb873c315" @@ -341,6 +1085,25 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/middleware-logger@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.40.0.tgz#29d9616bd39dafa1493cef333a32363e4df2c607" + integrity sha512-19kx0Xg5ymVRKoupmhdmfTBkROcv3DZj508agpyG2YAo0abOObMlIP4Jltg0VD4PhNjGzNh0jFGJnvhjdwv4/A== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-retry@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.35.0.tgz#35b80a1984b4b1bd0d113c1a05942c46888990db" + integrity sha512-tDSvXp6CDnuvYzKqryzxgm+z5BQ9RrrPrY1qj/K0CkenJTrBF+YU/DzhADpKFkUVE8VY5XdRFEcydnLw82oEtQ== + dependencies: + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/service-error-classification" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + uuid "^8.3.2" + "@aws-sdk/middleware-retry@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.38.0.tgz#525d6a9bcb5456759b1e7956c98d24039f59841b" @@ -352,6 +1115,52 @@ tslib "^2.3.0" uuid "^8.3.2" +"@aws-sdk/middleware-retry@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.40.0.tgz#5cffe046b1fd208a62a09495de6659be48ef86f3" + integrity sha512-SMUJrukugLL7YJE5X8B2ToukxMWMPwnf7jAFr84ptycCe8bdWv8x8klQ3EtVWpyqochtNlbTi6J/tTQBniUX7A== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/service-error-classification" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + uuid "^8.3.2" + +"@aws-sdk/middleware-sdk-ec2@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-ec2/-/middleware-sdk-ec2-3.35.0.tgz#b28bef13b8a807c5baafff6b1a6cdf5bd4e06cf5" + integrity sha512-2N0zMie4zok2CgUglqA8ezCXyeA4eyRAjSVCqjRrSaxykqVwxZ0OCWdcRIt2j6Wl8rLp4hjp/UAu+i8Z6C/4oQ== + dependencies: + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/signature-v4" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-format-url" "3.35.0" + "@aws-sdk/util-uri-escape" "3.35.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-sdk-s3@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.41.0.tgz#cd97433335af8436d6224194e9735bdecae28889" + integrity sha512-B7JOpmIpm1zxERQEMwZCWj3FisTvwfUgpfQglYdqrB6VpIoCM8fk2pmi5KzU/JDeNBlhTouj6mwnhJL/z5jopA== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/signature-v4" "3.40.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-arn-parser" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-sdk-sts@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.35.0.tgz#5bbaf654e24727df738f37558e872a11e6b1a3b2" + integrity sha512-lFScLmQ5yr9vKLb1+MPbRB/weQiHS2CgozKbFNeHTzSEKKHFwSmt35y7lLE1mkaO4l/No/Vl4e5A4ctyLFJn5w== + dependencies: + "@aws-sdk/middleware-signing" "3.35.0" + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/signature-v4" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/middleware-sdk-sts@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.38.0.tgz#6445e08bd2b645f86caa116b19da77e48f79762f" @@ -364,6 +1173,26 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/middleware-sdk-sts@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.40.0.tgz#3efefc29176d5078915b61d17105f8bbee86ff5e" + integrity sha512-TcrbCvj1PkabFZiNczT3yePZtuEm2fAIw1OVnQyLcF2KW+p62Hv5YkK4MPOfx3LA/0lzjOUO1RNl2x7gzV443Q== + dependencies: + "@aws-sdk/middleware-signing" "3.40.0" + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/signature-v4" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-serde@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.35.0.tgz#40b353519f86c912bad48af2bc840ba2f2e7406a" + integrity sha512-qvPw0GvoWjWYTDyNZTEkzIL//VDs1gSL9VKFo72xPqW9sLBu4yQu+Z1SUMzdfFbnmX64RqT6rpn4NLCIUOQqUg== + dependencies: + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/middleware-serde@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.38.0.tgz#fb081ab2a889569ffda960af89c725b4b8036dd1" @@ -372,6 +1201,25 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/middleware-serde@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.40.0.tgz#90124ff60a7f23963bbcd00a5cc95862b29dddd9" + integrity sha512-uOWfZjlAoBy6xPqp0d4ka83WNNbEVCWn9WwfqBUXThyoTdTooYSpXe5y2YzN0BJa8b+tEZTyWpgamnBpFLp47g== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-signing@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.35.0.tgz#feea3686aa84adf13a5e148ac9445d4e8e5ba3aa" + integrity sha512-U2JRfL9mo7e5XeSYp1VuiwTUDT5rgs2/2TCzOr+AI+Jy6TcLSSYXuW2l2yCrHzZJ27XSVhxF9/buNIAfYKwmaA== + dependencies: + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/signature-v4" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/middleware-signing@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.38.0.tgz#3fb6120eb1b58dda835680e5c26d864b9044d982" @@ -383,6 +1231,32 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/middleware-signing@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.40.0.tgz#bcbf5558a91db85a87918d5861ce98f306e40a88" + integrity sha512-RqK5nPbfma0qInMvjtpVkDYY/KkFS6EKlOv3DWTdxbXJ4YuOxgKiuUromhmBUoyjFag0JO7LUWod07H+/DawoA== + dependencies: + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/signature-v4" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-ssec@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.40.0.tgz#e6877cbabc9532ca85e017f6521924c591d01520" + integrity sha512-ZoRpaZeAIQa1Q+NyEh74ATwOR3nFGfcP6Nu0jFzgqoVijCReMnhtlCRx23ccBu1ZLZNUsNk6MhKjY+ZTfNsjEg== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/middleware-stack@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.35.0.tgz#c5851d8d489f1524f7720fb256a7b6b37d3180a3" + integrity sha512-K0xR73JSpFwpF4GIeCFC+TmD3YevceE+V8IYniDIxgCnBLhaIC1gXfTk7rDwxFnEqEpJt1To4MQBFYcsfIEQVw== + dependencies: + tslib "^2.3.0" + "@aws-sdk/middleware-stack@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.38.0.tgz#9b4f297e91adc390be2ea5c1c059ee956693cf05" @@ -390,6 +1264,22 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/middleware-stack@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.40.0.tgz#5aa614e49a4fc76cc63986fb45302f7afab6db87" + integrity sha512-hby9HvESUYJxpdALX+6Dn2LPmS5jtMVurGB/+j3MWOvIcDYB4bcSXgVRvXzYnTKwbSupIdbX9zOE2ZAx2SJpUQ== + dependencies: + tslib "^2.3.0" + +"@aws-sdk/middleware-user-agent@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.35.0.tgz#072b645cc836a07c36aef99fd78bc0906f3fb8e6" + integrity sha512-tKjQdnsKSKDTgjXAQxUyyz7CUX3IWtdqKSZoinfaljETeTANFUHoL60LERVMXP2RKayEQwV6T6BVYbJkonBxSQ== + dependencies: + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/middleware-user-agent@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.38.0.tgz#9d175404d20b2d46190a28b0a2c46a7338f3512a" @@ -399,6 +1289,25 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/middleware-user-agent@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.40.0.tgz#bf03d2deddc00689c85e7eadd9b4e02f24b61c08" + integrity sha512-dzC2fxWnanetFJ1oYgil8df3N36bR1yc/OCOpbdfQNiUk1FrXiCXqH5rHNO8zCvnwJAj8GHFwpFGd9a2Qube2w== + dependencies: + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/node-config-provider@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.35.0.tgz#f8e5cc3daf165919cb0a3b75cd34fd0dd83d5cab" + integrity sha512-lLzTK00QVy40bUR06Msl37gsLKQgVqiqEithgsSCfKr7XKyE39dy25xUDM+NhYN47HnVAnKnqGcaUb/Cscad0g== + dependencies: + "@aws-sdk/property-provider" "3.35.0" + "@aws-sdk/shared-ini-file-loader" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/node-config-provider@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.38.0.tgz#7fbc95138e8c5a2875a719aa73f1f83227ac6ca7" @@ -409,6 +1318,27 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/node-config-provider@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.40.0.tgz#54a8abc4f6d78503093b270e6dff3d6174c59f95" + integrity sha512-AmokjgUDECG8osoMfdRsPNweqI+L1pn4bYGk5iTLmzbBi0o4ot0U1FdX8Rf0qJZZwS4t1TXc3s8/PDVknmPxKg== + dependencies: + "@aws-sdk/property-provider" "3.40.0" + "@aws-sdk/shared-ini-file-loader" "3.37.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/node-http-handler@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.35.0.tgz#338920c743ce2c1a45d3cbd290598da31c1d179a" + integrity sha512-v/7YSNv/+KgWt+2OEKKbjpBhOZrwvah8rwO48XVQkGzgwuyjpjUKdSWIDKnrB6vWeoST66GGlZUmZ2IXQE4nSQ== + dependencies: + "@aws-sdk/abort-controller" "3.35.0" + "@aws-sdk/protocol-http" "3.35.0" + "@aws-sdk/querystring-builder" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/node-http-handler@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.38.0.tgz#00695d7ef8485819cab635e6e1838d8522d96343" @@ -420,6 +1350,25 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/node-http-handler@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.40.0.tgz#26491f11dabbd673c6318376d06af154adc123df" + integrity sha512-qjda6IbxDhbYr8NHmrMurKkbjgLUkfTMVgagDErDK24Nm3Dn5VaO6J4n6c0Q4OLHlmFaRcUfZSTrOo5DAubqCw== + dependencies: + "@aws-sdk/abort-controller" "3.40.0" + "@aws-sdk/protocol-http" "3.40.0" + "@aws-sdk/querystring-builder" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/property-provider@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.35.0.tgz#70955da41585ba3e920b6d905a02f9cb0b90db62" + integrity sha512-RiYsNFRmJ+OEeigYQowIedsQlk96uWm4TpZVVfkF7Z3wFpIPcc1e/0yKiNw1dj8iGqrFBmHdXwm2yXdRZPi6iA== + dependencies: + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/property-provider@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.38.0.tgz#e7e6625d7f08b7a042ecbe1e6d591553d0bc9957" @@ -428,6 +1377,22 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/property-provider@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.40.0.tgz#243cb1e87e36b1123ddc66d40d344e7580f80470" + integrity sha512-Mx4lkShjsYRwW9ujHA1pcnuubrWQ4kF5/DXWNfUiXuSIO/0Lojp1qTLheyBm4vzkJIlx5umyP6NvRAUkEHSN4Q== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/protocol-http@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.35.0.tgz#adfb62fb3ad1d02554bd3fae8d1e66b8849e7dff" + integrity sha512-rRj5bGX5K5qD5qufc0/kXoh6PWB3YLLeaC/oO5j90vadeztJJc2o9ZxOdJAYQ6YezwpqjD2/IEMLBO1Ab21bcg== + dependencies: + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/protocol-http@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.38.0.tgz#d297701c0e1235c5fbec6f014d5d8cbda2d2b409" @@ -436,6 +1401,23 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/protocol-http@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.40.0.tgz#ce6c7170a59e0a0eb63df5cd7cec87fe05bae680" + integrity sha512-f4ea7/HZkjpvGBrnRIuzc/bhrExWrgDv7eulj4htPukZGHdTqSJD3Jk8lEXWvFuX2vUKQDGhEhCDsqup7YWJQQ== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/querystring-builder@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.35.0.tgz#5d896237974c92cd171e1d47416f491fb67481e4" + integrity sha512-OdNTZDL0+NKUfnQj2vIju3MU9mbo6A/OIOmH+6CsKfN5psyChBmF7ue3Hn0tEnt3KoJLr38+n3MmDS2LPyoM0g== + dependencies: + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-uri-escape" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/querystring-builder@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.38.0.tgz#160b8ba5257d4fc28e9355b29e2992de950a1b1c" @@ -445,6 +1427,23 @@ "@aws-sdk/util-uri-escape" "3.37.0" tslib "^2.3.0" +"@aws-sdk/querystring-builder@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.40.0.tgz#f57212e60519d2d79ce6173cbe00fbe17a69bc0d" + integrity sha512-gO24oipnNaxJRBXB7lhLfa96vIMOd8gtMBqJTjelTjS2e1ZP1YY12CNKKTWwafSk8Ge021erZAG/YTOaXGpv+g== + dependencies: + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-uri-escape" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/querystring-parser@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.35.0.tgz#b019fe65265c52365cf2285f7799e04cb87a2b81" + integrity sha512-JjsBq+0b5MaX+jABFbmYIYo+mv+LOyAnh6G/QUMWbmBVhLL5E7U7FGa+HLXXs8YstbverBkoT7NuNpjtE8euIg== + dependencies: + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/querystring-parser@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.38.0.tgz#348be56bd1f7db63d5faa7012ee57cb42ecd766e" @@ -453,11 +1452,36 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/querystring-parser@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.40.0.tgz#5a5ba9c095ad3125a0daf37c33ed1cc8a600d53e" + integrity sha512-XZIyaKQIiZAM6zelCBcsLHhVDOLafi7XIOd3jy6SymGN8ajj3HqUJ/vdQ5G6ISTk18OrqgqcCOI9oNzv+nrBcA== + dependencies: + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/service-error-classification@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.35.0.tgz#f6269486c8117f4c8c9220ba9b0eea37747b8ed2" + integrity sha512-UvTx++RxkzJWGI5pcC8UN6X0CILYK+oVEHOtFghH93NEWNYww0ax+T86zeGxe47xqLZ8DX450jktqDN9I6/ntQ== + "@aws-sdk/service-error-classification@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.38.0.tgz#76aa14380809676e1d30ea21f9b7d85031a09a06" integrity sha512-/lWkibTVZz2+/CwembYJ+ETMVlwFWF7UBKdwa6xRIbE+sp74c1li1L6d/PU83PolAt86bLTXaKpdpMsj+d1WAg== +"@aws-sdk/service-error-classification@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.40.0.tgz#c98cbb781bd50e5d90649742ff954d754201c44d" + integrity sha512-c8btKmkvjXczWudXubGdbO3JgmjySBUVC/gCrZDNfwNGsG8RYJJQYYcnmt1gWjelUZsgMDl/2PIzxTlxVF91rA== + +"@aws-sdk/shared-ini-file-loader@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.35.0.tgz#12ea2a55d27b3531f56729fdcd8df708e50beb55" + integrity sha512-hP6E0MY6Yxc+ClplKF37w5xgNQ80Jc1m8/stTDc9+PBVv3kHKL4qJmpeAg3lIqWjeQ7Kj3Pw6McNGqTyNTnZOg== + dependencies: + tslib "^2.3.0" + "@aws-sdk/shared-ini-file-loader@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.37.0.tgz#ca595d9745150f46805f68be6a6c1607d618ad94" @@ -465,6 +1489,17 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/signature-v4@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.35.0.tgz#b3734b02d6b4a8fcdd09160a38785a2efe782517" + integrity sha512-BU7PR0b0ecMSmP5Vd29ZzGdiJhaX56orQ2eb73b5CzR+v08Pb9xLLce0qaN+4opULxIbP2M+8OfUZN9l3Gat9g== + dependencies: + "@aws-sdk/is-array-buffer" "3.35.0" + "@aws-sdk/types" "3.35.0" + "@aws-sdk/util-hex-encoding" "3.35.0" + "@aws-sdk/util-uri-escape" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/signature-v4@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.38.0.tgz#02bf4eb839fa7e49a04e65de12b224a870c9b789" @@ -476,6 +1511,26 @@ "@aws-sdk/util-uri-escape" "3.37.0" tslib "^2.3.0" +"@aws-sdk/signature-v4@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.40.0.tgz#9de1b4e1130f68394df3232882805896c2d20e45" + integrity sha512-Q1GNZJRCS3W2qsRtDsX/b6EOSfMXfr6TW46N3LnLTGYZ3KAN2SOSJ1DsW59AuGpEZyRmOhJ9L/Q5U403+bZMXQ== + dependencies: + "@aws-sdk/is-array-buffer" "3.37.0" + "@aws-sdk/types" "3.40.0" + "@aws-sdk/util-hex-encoding" "3.37.0" + "@aws-sdk/util-uri-escape" "3.37.0" + tslib "^2.3.0" + +"@aws-sdk/smithy-client@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.35.0.tgz#ad1eedb1408775f9062f6aad6a6a7e4e1c60472d" + integrity sha512-HZLrSLb4tMt0VUzeAMaQPMXOfAXzRAV30+KBhD11bbb4agrOcamfA3Fzwl5xNrVyJRpcJ9tbvUN8moHaavNTVg== + dependencies: + "@aws-sdk/middleware-stack" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/smithy-client@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.38.0.tgz#a756b8bd8608e9ff9b69ba27f83d5deb51fd9f1b" @@ -485,11 +1540,39 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" -"@aws-sdk/types@3.38.0", "@aws-sdk/types@^3.1.0": +"@aws-sdk/smithy-client@3.41.0": + version "3.41.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.41.0.tgz#61154b4813a01dc079e7083805a20e1bc05d3199" + integrity sha512-ldhS0Pf3v6yHCd//kk5DvKcdyeUkKEwxNDRanAp+ekTW68J3XcYgKaPC9sNDhVTDH1zrywTvtEz5zWHEvXjQow== + dependencies: + "@aws-sdk/middleware-stack" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/types@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.35.0.tgz#15515921490a2adcc039071f0b724b13551563c3" + integrity sha512-6HGiPfw2hgs9q+rSFUqIs7g7V1BmQAwJYiZJ1XexVX1BgqvVe9zcRvyLqrv5zeWM/zj4pJ7q6JBoXjlQWb1ybw== + +"@aws-sdk/types@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.38.0.tgz#16b3c2d78512d7f193edb519de46c5ef00fffd2b" integrity sha512-Opux3HLwMlWb7GIJxERsOnmbHrT2A1gsd8aF5zHapWPPH5Z0rYsgTIq64qgim896XlKlOw6/YzhD5CdyNjlQWg== +"@aws-sdk/types@3.40.0", "@aws-sdk/types@^3.1.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.40.0.tgz#a9d7926fcb9b699bc46be975033559d2293e60d1" + integrity sha512-KpILcfvRaL88TLvo3SY4OuCCg90SvcNLPyjDwUuBqiOyWODjrKShHtAPJzej4CLp92lofh+ul0UnBfV9Jb/5PA== + +"@aws-sdk/url-parser@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.35.0.tgz#1830f1fc6487c56671167c175dada52de8e36fce" + integrity sha512-FquGiP8qRohixhonE8til6e6V8IRZEyXIFdrItN48YMfQPynYiP+k9T9uSpXrhZUpnd0vbMdkjnYOpGVUKNtNA== + dependencies: + "@aws-sdk/querystring-parser" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/url-parser@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.38.0.tgz#32bea12a3f4a1e3b4f83987ffb5543347b1b9d14" @@ -499,6 +1582,29 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/url-parser@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.40.0.tgz#9ccd00a2026605d5eaef630e94b6632cc9598ec3" + integrity sha512-HwNV+HX7bHgLk5FzTOgdXANsC0SeVz5PMC4Nh+TLz2IoeQnrw4H8dsA4YNonncjern5oC5veKRjQeOoCL5SlSQ== + dependencies: + "@aws-sdk/querystring-parser" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/util-arn-parser@3.37.0": + version "3.37.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.37.0.tgz#807691690c4c52c1e8e0a362bf5767291b626f1f" + integrity sha512-njIYn8gzm7Ms17A2oEu0vN/0GJpgq7cNFFtzBrM1cPtrc1jhMRJx5hzS7uX5h6ll8BM92bA3y00evRZFHxQPVQ== + dependencies: + tslib "^2.3.0" + +"@aws-sdk/util-base64-browser@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-browser/-/util-base64-browser-3.35.0.tgz#a0ea985eb5377f30b1ad1797707f1b2e1c66d06a" + integrity sha512-MwptHeiZcYFEJddxeH9xq06T1jmdCLDOerdAa7aSlWstONzarm/YjYlJKxZLrv6IkWmPUCzMpMoc3T1LppPHOg== + dependencies: + tslib "^2.3.0" + "@aws-sdk/util-base64-browser@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-browser/-/util-base64-browser-3.37.0.tgz#4bf105de91e5e17ded644557dac6851c30e992d2" @@ -506,6 +1612,14 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/util-base64-node@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-node/-/util-base64-node-3.35.0.tgz#5bad910a73a4b56fd6fc7b8554f74a95881eda72" + integrity sha512-NgKGFj+6yjdOqVl9A7mbsN2mh5G1YAE7F3Ct92pFFhCYsDKMCtxVVIDT5zA7Cq9c/b2OGBi2SUcr/xiljeuHLw== + dependencies: + "@aws-sdk/util-buffer-from" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/util-base64-node@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-node/-/util-base64-node-3.37.0.tgz#81ff164d227db8faeb910af33ff5f861269d6d67" @@ -514,6 +1628,13 @@ "@aws-sdk/util-buffer-from" "3.37.0" tslib "^2.3.0" +"@aws-sdk/util-body-length-browser@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.35.0.tgz#752df024c6d23b8e89e91fa8f350689aaedfd491" + integrity sha512-mZMLMxPYDBkeavQGTpXkA9aoGyhOOw8upZcmuXbKBTsOYozeKctbjtrfNbjDI5hHl2+s8e29mDijx5zmokvnww== + dependencies: + tslib "^2.3.0" + "@aws-sdk/util-body-length-browser@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.37.0.tgz#2e3a375ac191a9bacd40a6b3479ee402dcb5769d" @@ -521,6 +1642,13 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/util-body-length-node@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-node/-/util-body-length-node-3.35.0.tgz#0d696626dde1656db910a19847b324a88cf30cef" + integrity sha512-FNsk1ckQhi0fLpWdH4IYj1Ds6vvkvu/dk21kzZNAc4DflC+vhMtILJij2C23JBc2tKstHavgxiKUlmULWxKecA== + dependencies: + tslib "^2.3.0" + "@aws-sdk/util-body-length-node@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-node/-/util-body-length-node-3.37.0.tgz#d6170dafd351799687d583f818a4a3924b61cbec" @@ -528,6 +1656,14 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/util-buffer-from@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-buffer-from/-/util-buffer-from-3.35.0.tgz#a37d27a3486304752b60d47f74589266c63148c8" + integrity sha512-wS74/p3lwNS9eLDRKjNCyaEux01b6R0yEg5+kXxvLqK1wcAHYhuVC+Hk4Plg5kP96WwGvfiRp5duU2P4RkxicQ== + dependencies: + "@aws-sdk/is-array-buffer" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/util-buffer-from@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-buffer-from/-/util-buffer-from-3.37.0.tgz#298d4a925b9f0ca23f99617648cd9fb3896b573c" @@ -536,6 +1672,21 @@ "@aws-sdk/is-array-buffer" "3.37.0" tslib "^2.3.0" +"@aws-sdk/util-config-provider@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-config-provider/-/util-config-provider-3.40.0.tgz#acefff264d6650450a1f8b056a63830a454b756d" + integrity sha512-NjZGrA4mqhpr6gkVCAUweurP0Z9d3vFyXJCtulC0BFbpKAnKCf/crSK56NwUaNhAEMCkSuBvjRFzkbfT+HO8bA== + dependencies: + tslib "^2.3.0" + +"@aws-sdk/util-credentials@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-credentials/-/util-credentials-3.35.0.tgz#186d12e59228257eccbf316046ef0dab5ecf5ffb" + integrity sha512-n8HN0oHfVvwT2OMLd9x5cyWofalAfP66zXdFJr9UlrApqvKqozLIdAgU1YaQFvzQETLjq+626INJjdWWgdsEjA== + dependencies: + "@aws-sdk/shared-ini-file-loader" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/util-credentials@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-credentials/-/util-credentials-3.37.0.tgz#76261c3d7c20bee5d28e5c17741adf19558b3b67" @@ -544,6 +1695,22 @@ "@aws-sdk/shared-ini-file-loader" "3.37.0" tslib "^2.3.0" +"@aws-sdk/util-format-url@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-format-url/-/util-format-url-3.35.0.tgz#2b2b6516df4427e8b8ce449b9abe923ed4656006" + integrity sha512-57hHbSWw95DchH2LgZfDZ1luUeZt3ZcTCDtwMviUStCEcDgPsHmSLLkND1+ac+mRuXIHtWCGsHc0J5xWFUBMvw== + dependencies: + "@aws-sdk/querystring-builder" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + +"@aws-sdk/util-hex-encoding@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.35.0.tgz#07f1d6facb828c1faebc88b819536b41992e872a" + integrity sha512-umoRRRL/elzwsMz81qCRNzFZzEfMD+Lk1bawJDmJQ/gmc3vBV9KK4CzjqKPEUbQmDhqm7X2IGBDY32oKJ8CTQg== + dependencies: + tslib "^2.3.0" + "@aws-sdk/util-hex-encoding@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.37.0.tgz#40ce21b5ff682e811e98ac7476692ee55ae61493" @@ -558,6 +1725,13 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/util-uri-escape@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-uri-escape/-/util-uri-escape-3.35.0.tgz#8904bea6f3c281bff87353437c01f8ddfbe87aa1" + integrity sha512-5+4Pb4+SqQTyd/E1P2NG1TwOeRGDOdBzS4m880i96GVNgGUjWWzCqKY+JC/AUtpv2IG6VppRUVWGnp7Pz8y3qg== + dependencies: + tslib "^2.3.0" + "@aws-sdk/util-uri-escape@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-uri-escape/-/util-uri-escape-3.37.0.tgz#42b8393a51dcc04f228e70d1c94c2fe38a738994" @@ -565,6 +1739,15 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/util-user-agent-browser@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.35.0.tgz#157345d84245491b72461a726b1662e1ea2668a4" + integrity sha512-TdH2V+kVxKWT1YIxS094oeOegvRDOXPB4kfbJr43oWdrVi9F5a5Ub/ZWeofkczRUr7hEq6gK8pwzDEPgp1lp0Q== + dependencies: + "@aws-sdk/types" "3.35.0" + bowser "^2.11.0" + tslib "^2.3.0" + "@aws-sdk/util-user-agent-browser@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.38.0.tgz#bdc689e57c2d144fa76d997075d01398bb51e790" @@ -574,6 +1757,24 @@ bowser "^2.11.0" tslib "^2.3.0" +"@aws-sdk/util-user-agent-browser@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.40.0.tgz#d9f4f49af35895df260598a333a8b792b56e9f76" + integrity sha512-C69sTI26bV2EprTv3DTXu9XP7kD9Wu4YVPBzqztOYArd2GDYw3w+jS8SEg3XRbjAKY/mOPZ2Thw4StjpZlWZiA== + dependencies: + "@aws-sdk/types" "3.40.0" + bowser "^2.11.0" + tslib "^2.3.0" + +"@aws-sdk/util-user-agent-node@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.35.0.tgz#1aadf61587aa70706c947a494cd9a281b2fd7586" + integrity sha512-6xR7elRJtADzhKIJINqmHve39PM8KlwdoxyX1X4T+NVa+iscj5V7iLMyv82o2EpkpBg0nYosuw0HPOBMlHQ1/w== + dependencies: + "@aws-sdk/node-config-provider" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/util-user-agent-node@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.38.0.tgz#4f98a5287b9c3d25e3badaa29a1fbee517f72ea4" @@ -583,6 +1784,22 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" +"@aws-sdk/util-user-agent-node@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.40.0.tgz#76240a4ee05e409ad1267854761c53e746e9bcdf" + integrity sha512-cjIzd0hRZFTTh7iLJD6Bciu++Em1iaM1clyG02xRl0JD5DEtDSR1zO02uu+AeM7GSLGOxIvwOkK2j8ySPAOmBA== + dependencies: + "@aws-sdk/node-config-provider" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/util-utf8-browser@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.35.0.tgz#7a38a11badd5f8e50f1c1f49eb8684693359b314" + integrity sha512-STSEn8uyh6FbMDnHAdhYS7R25i0mDtryZXJDEeEfKJkNJmqK5bsWIdcYxsr0y4Gj3yy15snJNYL8S3lJxv2mcQ== + dependencies: + tslib "^2.3.0" + "@aws-sdk/util-utf8-browser@3.37.0", "@aws-sdk/util-utf8-browser@^3.0.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.37.0.tgz#d896899f4c475ceeaf8b77c5d7cdc453e5fe6b83" @@ -590,6 +1807,14 @@ dependencies: tslib "^2.3.0" +"@aws-sdk/util-utf8-node@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-node/-/util-utf8-node-3.35.0.tgz#81e3ba82e367278ad88dcc619a82ac5178a5b927" + integrity sha512-mc8TVu1TzbN4YSf8UsoNGF8LSfnt1HONpOgfc664x5OqtU6874qlyJg2FEFMwVQMcYnzZH64FlMWk5ZDbz3UFQ== + dependencies: + "@aws-sdk/util-buffer-from" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/util-utf8-node@3.37.0": version "3.37.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-node/-/util-utf8-node-3.37.0.tgz#300912cce55d72c18213190237d6ab943e17b5bf" @@ -598,6 +1823,15 @@ "@aws-sdk/util-buffer-from" "3.37.0" tslib "^2.3.0" +"@aws-sdk/util-waiter@3.35.0": + version "3.35.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.35.0.tgz#8c6dc9e95feb848f51d091b043096e7f3db5f2f5" + integrity sha512-hnOkWorTjr0BnpoCiGJNiHU9D6iKUJrVp+TOMH4AqRcdfBpPNJpeFUlFmAsT03og7IiyjXRcxHiVC5Gx5pGDDw== + dependencies: + "@aws-sdk/abort-controller" "3.35.0" + "@aws-sdk/types" "3.35.0" + tslib "^2.3.0" + "@aws-sdk/util-waiter@3.38.0": version "3.38.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.38.0.tgz#e7406ceb556e439a8c9918b8c90aefb58f882b5b" @@ -607,12 +1841,21 @@ "@aws-sdk/types" "3.38.0" tslib "^2.3.0" -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== +"@aws-sdk/util-waiter@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.40.0.tgz#91c537efc9d0129fb24d9bdab86acbfd797ddf1f" + integrity sha512-jdxwNEZdID49ZvyAnxaeNm5w2moIfMLOwj/q6TxKlxYoXMs16FQWkhyfGue0vEASzchS49ewbyt+KBqpT31Ebg== + dependencies: + "@aws-sdk/abort-controller" "3.40.0" + "@aws-sdk/types" "3.40.0" + tslib "^2.3.0" + +"@aws-sdk/xml-builder@3.37.0": + version "3.37.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.37.0.tgz#17aceb34188a58b5c25b49344f66135b0fe95f79" + integrity sha512-Vf0f4ZQ+IBo/l9wUaTOXLqqQO9b/Ll5yPbg+EhHx8zlHbTHIm89ettkVCGyT/taGagC1X+ZeTK9maX6ymEOBow== dependencies: - "@babel/highlight" "^7.10.4" + tslib "^2.3.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0": version "7.16.0" @@ -622,9 +1865,9 @@ "@babel/highlight" "^7.16.0" "@babel/compat-data@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.0.tgz#ea269d7f78deb3a7826c39a4048eecda541ebdaa" - integrity sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew== + version "7.16.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.7.5": version "7.16.0" @@ -657,13 +1900,13 @@ source-map "^0.5.0" "@babel/helper-compilation-targets@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.0.tgz#01d615762e796c17952c29e3ede9d6de07d235a8" - integrity sha512-S7iaOT1SYlqK0sQaCi21RX4+13hmdmnxIEAnQUB/eh7GeAnRjOUgTYpLkUOiRXzD+yog1JxP0qyAQZ7ZxVxLVg== + version "7.16.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" + integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== dependencies: "@babel/compat-data" "^7.16.0" "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.16.6" + browserslist "^4.17.5" semver "^6.3.0" "@babel/helper-function-name@^7.16.0": @@ -764,15 +2007,15 @@ integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== "@babel/helpers@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.0.tgz#875519c979c232f41adfbd43a3b0398c2e388183" - integrity sha512-dVRM0StFMdKlkt7cVcGgwD8UMaBfWJHl3A83Yfs8GQ3MO0LHIIIMvK7Fa0RGOGUQ10qikLaX6D7o5htcQWgTMQ== + version "7.16.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.3.tgz#27fc64f40b996e7074dc73128c3e5c3e7f55c43c" + integrity sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w== dependencies: "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.0" + "@babel/traverse" "^7.16.3" "@babel/types" "^7.16.0" -"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": +"@babel/highlight@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== @@ -781,10 +2024,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.7.2": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.0.tgz#cf147d7ada0a3655e79bf4b08ee846f00a00a295" - integrity sha512-TEHWXf0xxpi9wKVyBCmRcSSDjbJ/cl6LUdlbYUHEaNQUJGhreJbZrXT6sR4+fZLxVUJqNRB4KyOvjuy/D9009A== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3", "@babel/parser@^7.7.2": + version "7.16.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" + integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -878,9 +2121,9 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/runtime-corejs3@^7.12.1": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.16.0.tgz#58a7fb00e6948508f12f53a303993e8b6e2f6c70" - integrity sha512-Oi2qwQ21X7/d9gn3WiwkDTJmq3TQtYNz89lRnoFy8VeZpWlsyXvzSwiRrRZ8cXluvSwqKxqHJ6dBd9Rv+p0ZGQ== + version "7.16.3" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.16.3.tgz#1e25de4fa994c57c18e5fdda6cc810dac70f5590" + integrity sha512-IAdDC7T0+wEB4y2gbIL0uOXEYpiZEeuFUTVbdGq+UwCcF35T/tS8KrmMomEwEc5wBbyfH3PJVpTSUqrhPDXFcQ== dependencies: core-js-pure "^3.19.0" regenerator-runtime "^0.13.4" @@ -894,17 +2137,17 @@ "@babel/parser" "^7.16.0" "@babel/types" "^7.16.0" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.7.2": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.0.tgz#965df6c6bfc0a958c1e739284d3c9fa4a6e3c45b" - integrity sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.7.2": + version "7.16.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787" + integrity sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag== dependencies: "@babel/code-frame" "^7.16.0" "@babel/generator" "^7.16.0" "@babel/helper-function-name" "^7.16.0" "@babel/helper-hoist-variables" "^7.16.0" "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/parser" "^7.16.0" + "@babel/parser" "^7.16.3" "@babel/types" "^7.16.0" debug "^4.1.0" globals "^11.1.0" @@ -939,18 +2182,18 @@ dependencies: "@cspotcode/source-map-consumer" "0.8.0" -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== +"@eslint/eslintrc@^1.0.4": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== dependencies: ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" + debug "^4.3.2" + espree "^9.2.0" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" - js-yaml "^3.13.1" + js-yaml "^4.1.0" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -959,26 +2202,26 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== +"@humanwhocodes/config-array@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.6.0.tgz#b5621fdb3b32309d2d16575456cbc277fa8f021a" + integrity sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A== dependencies: "@humanwhocodes/object-schema" "^1.2.0" debug "^4.1.1" minimatch "^3.0.4" "@humanwhocodes/object-schema@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@iarna/toml@2.2.5": version "2.2.5" resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== -"@isaacs/string-locale-compare@*", "@isaacs/string-locale-compare@^1.0.1": +"@isaacs/string-locale-compare@*", "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== @@ -999,93 +2242,93 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.3.1.tgz#e8ea3a475d3f8162f23d69efbfaa9cbe486bee93" - integrity sha512-RkFNWmv0iui+qsOr/29q9dyfKTTT5DCuP31kUwg7rmOKPT/ozLeGLKJKVIiOfbiKyleUZKIrHwhmiZWVe8IMdw== +"@jest/console@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.4.2.tgz#7a95612d38c007ddb528ee446fe5e5e785e685ce" + integrity sha512-xknHThRsPB/To1FUbi6pCe43y58qFC03zfb6R7fDb/FfC7k2R3i1l+izRBJf8DI46KhYGRaF14Eo9A3qbBoixg== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^27.3.1" - jest-util "^27.3.1" + jest-message-util "^27.4.2" + jest-util "^27.4.2" slash "^3.0.0" -"@jest/core@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.3.1.tgz#04992ef1b58b17c459afb87ab56d81e63d386925" - integrity sha512-DMNE90RR5QKx0EA+wqe3/TNEwiRpOkhshKNxtLxd4rt3IZpCt+RSL+FoJsGeblRZmqdK4upHA/mKKGPPRAifhg== +"@jest/core@^27.4.2", "@jest/core@^27.4.3": + version "27.4.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.4.3.tgz#9b9b34f4e6429a633085f476402aa2e3ce707877" + integrity sha512-V9ms3zSxUHxh1E/ZLAiXF7SLejsdFnjWTFizWotMOWvjho0lW5kSjZymhQSodNW0T0ZMQRiha7f8+NcFVm3hJQ== dependencies: - "@jest/console" "^27.3.1" - "@jest/reporters" "^27.3.1" - "@jest/test-result" "^27.3.1" - "@jest/transform" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/console" "^27.4.2" + "@jest/reporters" "^27.4.2" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.8.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-changed-files "^27.3.0" - jest-config "^27.3.1" - jest-haste-map "^27.3.1" - jest-message-util "^27.3.1" - jest-regex-util "^27.0.6" - jest-resolve "^27.3.1" - jest-resolve-dependencies "^27.3.1" - jest-runner "^27.3.1" - jest-runtime "^27.3.1" - jest-snapshot "^27.3.1" - jest-util "^27.3.1" - jest-validate "^27.3.1" - jest-watcher "^27.3.1" + jest-changed-files "^27.4.2" + jest-config "^27.4.3" + jest-haste-map "^27.4.2" + jest-message-util "^27.4.2" + jest-regex-util "^27.4.0" + jest-resolve "^27.4.2" + jest-resolve-dependencies "^27.4.2" + jest-runner "^27.4.3" + jest-runtime "^27.4.2" + jest-snapshot "^27.4.2" + jest-util "^27.4.2" + jest-validate "^27.4.2" + jest-watcher "^27.4.2" micromatch "^4.0.4" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.3.1.tgz#2182defbce8d385fd51c5e7c7050f510bd4c86b1" - integrity sha512-BCKCj4mOVLme6Tanoyc9k0ultp3pnmuyHw73UHRPeeZxirsU/7E3HC4le/VDb/SMzE1JcPnto+XBKFOcoiJzVw== +"@jest/environment@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.4.2.tgz#03efabce528dbb09bffd3ec7e39bb0f3f7475cc2" + integrity sha512-uSljKxh/rGlHlmhyeG4ZoVK9hOec+EPBkwTHkHKQ2EqDu5K+MaG9uJZ8o1CbRsSdZqSuhXvJCYhBWsORPPg6qw== dependencies: - "@jest/fake-timers" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/fake-timers" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" - jest-mock "^27.3.0" + jest-mock "^27.4.2" -"@jest/fake-timers@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.3.1.tgz#1fad860ee9b13034762cdb94266e95609dfce641" - integrity sha512-M3ZFgwwlqJtWZ+QkBG5NmC23A9w+A6ZxNsO5nJxJsKYt4yguBd3i8TpjQz5NfCX91nEve1KqD9RA2Q+Q1uWqoA== +"@jest/fake-timers@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.4.2.tgz#d217f86c3ba2027bf29e0b731fd0cb761a72d093" + integrity sha512-f/Xpzn5YQk5adtqBgvw1V6bF8Nx3hY0OIRRpCvWcfPl0EAjdqWPdhH3t/3XpiWZqtjIEHDyMKP9ajpva1l4Zmg== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" "@sinonjs/fake-timers" "^8.0.1" "@types/node" "*" - jest-message-util "^27.3.1" - jest-mock "^27.3.0" - jest-util "^27.3.1" + jest-message-util "^27.4.2" + jest-mock "^27.4.2" + jest-util "^27.4.2" -"@jest/globals@27.3.1", "@jest/globals@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.3.1.tgz#ce1dfb03d379237a9da6c1b99ecfaca1922a5f9e" - integrity sha512-Q651FWiWQAIFiN+zS51xqhdZ8g9b88nGCobC87argAxA7nMfNQq0Q0i9zTfQYgLa6qFXk2cGANEqfK051CZ8Pg== +"@jest/globals@27.4.2", "@jest/globals@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.4.2.tgz#56a402c5ebf22eba1d34e900772147f5126ea2d8" + integrity sha512-KkfaHEttlGpXYAQTZHgrESiEPx2q/DKAFLGLFda1uGVrqc17snd3YVPhOxlXOHIzVPs+lQ/SDB2EIvxyGzb3Ew== dependencies: - "@jest/environment" "^27.3.1" - "@jest/types" "^27.2.5" - expect "^27.3.1" + "@jest/environment" "^27.4.2" + "@jest/types" "^27.4.2" + expect "^27.4.2" -"@jest/reporters@27.3.1", "@jest/reporters@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.3.1.tgz#28b5c1f5789481e23788048fa822ed15486430b9" - integrity sha512-m2YxPmL9Qn1emFVgZGEiMwDntDxRRQ2D58tiDQlwYTg5GvbFOKseYCcHtn0WsI8CG4vzPglo3nqbOiT8ySBT/w== +"@jest/reporters@27.4.2", "@jest/reporters@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.4.2.tgz#d3860c5d3f668fa1326ab2bf5989f774e5c03f04" + integrity sha512-sp4aqmdBJtjKetEakzDPcZggPcVIF6w9QLkYBbaWDV6e/SIsHnF1S4KtIH91eEc2fp7ep6V/e1xvdfEoho1d2w== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^27.3.1" - "@jest/test-result" "^27.3.1" - "@jest/transform" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/console" "^27.4.2" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -1097,60 +2340,60 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.0.2" - jest-haste-map "^27.3.1" - jest-resolve "^27.3.1" - jest-util "^27.3.1" - jest-worker "^27.3.1" + jest-haste-map "^27.4.2" + jest-resolve "^27.4.2" + jest-util "^27.4.2" + jest-worker "^27.4.2" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" -"@jest/source-map@^27.0.6": - version "27.0.6" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.0.6.tgz#be9e9b93565d49b0548b86e232092491fb60551f" - integrity sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g== +"@jest/source-map@^27.4.0": + version "27.4.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.4.0.tgz#2f0385d0d884fb3e2554e8f71f8fa957af9a74b6" + integrity sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ== dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" source-map "^0.6.0" -"@jest/test-result@27.3.1", "@jest/test-result@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.3.1.tgz#89adee8b771877c69b3b8d59f52f29dccc300194" - integrity sha512-mLn6Thm+w2yl0opM8J/QnPTqrfS4FoXsXF2WIWJb2O/GBSyResL71BRuMYbYRsGt7ELwS5JGcEcGb52BNrumgg== +"@jest/test-result@27.4.2", "@jest/test-result@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.4.2.tgz#05fd4a5466ec502f3eae0b39dff2b93ea4d5d9ec" + integrity sha512-kr+bCrra9jfTgxHXHa2UwoQjxvQk3Am6QbpAiJ5x/50LW8llOYrxILkqY0lZRW/hu8FXesnudbql263+EW9iNA== dependencies: - "@jest/console" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/console" "^27.4.2" + "@jest/types" "^27.4.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.3.1.tgz#4b3bde2dbb05ee74afdae608cf0768e3354683b1" - integrity sha512-siySLo07IMEdSjA4fqEnxfIX8lB/lWYsBPwNFtkOvsFQvmBrL3yj3k3uFNZv/JDyApTakRpxbKLJ3CT8UGVCrA== +"@jest/test-sequencer@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.4.2.tgz#94bb7e5412d59ae2a8a4b8f9925bb16b6dc82b4c" + integrity sha512-HmHp5mlh9f9GyNej5yCS1JZIFfUGnP9+jEOH5zoq5EmsuZeYD+dGULqyvGDPtuzzbyAFJ6R4+z4SS0VvnFwwGQ== dependencies: - "@jest/test-result" "^27.3.1" + "@jest/test-result" "^27.4.2" graceful-fs "^4.2.4" - jest-haste-map "^27.3.1" - jest-runtime "^27.3.1" + jest-haste-map "^27.4.2" + jest-runtime "^27.4.2" -"@jest/transform@^27.3.1": - version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.3.1.tgz#ff80eafbeabe811e9025e4b6f452126718455220" - integrity sha512-3fSvQ02kuvjOI1C1ssqMVBKJpZf6nwoCiSu00zAKh5nrp3SptNtZy/8s5deayHnqxhjD9CWDJ+yqQwuQ0ZafXQ== +"@jest/transform@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.4.2.tgz#459885e96de2e21fc68b8b371e90aa653966dd0d" + integrity sha512-RTKcPZllfcmLfnlxBya7aypofhdz05+E6QITe55Ex0rxyerkgjmmpMlvVn11V0cP719Ps6WcDYCnDzxnnJUwKg== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" babel-plugin-istanbul "^6.0.0" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.2.4" - jest-haste-map "^27.3.1" - jest-regex-util "^27.0.6" - jest-util "^27.3.1" + jest-haste-map "^27.4.2" + jest-regex-util "^27.4.0" + jest-util "^27.4.2" micromatch "^4.0.4" pirates "^4.0.1" slash "^3.0.0" @@ -1168,10 +2411,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jest/types@^27.0.6", "@jest/types@^27.2.5": - version "27.2.5" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.2.5.tgz#420765c052605e75686982d24b061b4cbba22132" - integrity sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ== +"@jest/types@^27.0.6", "@jest/types@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.4.2.tgz#96536ebd34da6392c2b7c7737d693885b5dd44a5" + integrity sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" @@ -1218,17 +2461,17 @@ fastq "^1.6.0" "@npmcli/arborist@*", "@npmcli/arborist@^4.0.0": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-4.0.3.tgz#5e1632192f970c3a4e43c4699ad875089418bed0" - integrity sha512-gFz/dNJtpv2bYXlupcUpEaWlFDRUNmvVnQNbE6dY4ild6beZ2SkG4R5/CM4GZZwj9HD2TyfGjO350Ja+xlLzuA== + version "4.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-4.1.0.tgz#436464664bcfb3b180963383c410ff9180351c59" + integrity sha512-bkaOqCuTUtpVOe1vaAP7TUihu64wIbnSDpsbqBJUsGFTLYXbjKwi6xj8Zx5cfHkM3nqyeEEbPYlGkt0TXjKrUg== dependencies: - "@isaacs/string-locale-compare" "^1.0.1" + "@isaacs/string-locale-compare" "^1.1.0" "@npmcli/installed-package-contents" "^1.0.7" "@npmcli/map-workspaces" "^2.0.0" "@npmcli/metavuln-calculator" "^2.0.0" "@npmcli/move-file" "^1.1.0" "@npmcli/name-from-folder" "^1.0.1" - "@npmcli/node-gyp" "^1.0.1" + "@npmcli/node-gyp" "^1.0.3" "@npmcli/package-json" "^1.0.1" "@npmcli/run-script" "^2.0.0" bin-links "^2.3.0" @@ -1242,7 +2485,7 @@ npm-package-arg "^8.1.5" npm-pick-manifest "^6.1.0" npm-registry-fetch "^11.0.0" - pacote "^12.0.0" + pacote "^12.0.2" parse-conflict-json "^1.1.1" proc-log "^1.0.0" promise-all-reject-late "^1.0.0" @@ -1261,9 +2504,9 @@ integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== "@npmcli/config@*": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-2.3.0.tgz#364fbe942037e562a832a113206e14ccb651f7bc" - integrity sha512-yjiC1xv7KTmUTqfRwN2ZL7BHV160ctGF0fLXmKkkMXj40UOvBe45Apwvt5JsFRtXSoHkUYy1ouzscziuWNzklg== + version "2.3.2" + resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-2.3.2.tgz#6027efc132fcc809abef749c2f2e13dc4dcd6e0b" + integrity sha512-2/9dj143BFgQR8qxJbYptd8k+4+Po2uHYq3H6498ynZcRu4LrsDlngov5HGrvo2+f0pe0fBJwDEP2rRtaW8bkw== dependencies: ini "^2.0.0" mkdirp-infer-owner "^2.0.0" @@ -1341,7 +2584,7 @@ resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== -"@npmcli/node-gyp@^1.0.1", "@npmcli/node-gyp@^1.0.2": +"@npmcli/node-gyp@^1.0.2", "@npmcli/node-gyp@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz#a912e637418ffc5f2db375e93b85837691a43a33" integrity sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA== @@ -1481,9 +2724,10 @@ dependencies: "@octokit/openapi-types" "^11.2.0" -"@renovate/eslint-plugin@https://github.com/renovatebot/eslint-plugin#v0.0.3": - version "0.0.1" - resolved "https://github.com/renovatebot/eslint-plugin#c88253170ce9e9248bc0653197ed2ff1ecf41ac1" +"@renovate/eslint-plugin@https://github.com/renovatebot/eslint-plugin#v0.0.4": + version "0.0.4" + uid "0c444386e79d6145901212507521b8a0a48af000" + resolved "https://github.com/renovatebot/eslint-plugin#0c444386e79d6145901212507521b8a0a48af000" "@renovate/pep440@1.0.0": version "1.0.0" @@ -1492,6 +2736,17 @@ dependencies: xregexp "4.4.1" +"@renovatebot/parser-utils@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@renovatebot/parser-utils/-/parser-utils-1.0.0.tgz#ff29ca07adc0f9d2f1221675d3ded444d6e5a20d" + integrity sha512-OTIsj9XY/OPNl6zhPt6QwUDcpJK1e1oS51xwXgM5Vj+O/nnQu6Ydw7bhnu9b6vEfFxwtuyU2A0B4sBS/ZNyppQ== + dependencies: + "@thi.ng/zipper" "1.0.3" + "@types/moo" "0.5.5" + deep-freeze-es6 "1.4.1" + klona "2.0.5" + moo "0.5.1" + "@renovatebot/ruby-semver@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@renovatebot/ruby-semver/-/ruby-semver-1.0.0.tgz#79b5b8edbfa09b9c7c99a1c60b0fc68af7bae9b4" @@ -1499,20 +2754,20 @@ dependencies: tslib "2.1.0" -"@semantic-release/commit-analyzer@^9.0.0": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.1.tgz#e9b75a966898cae36493c7eb8158135eb302e270" - integrity sha512-ncNsnrLmiykhgNZUXNvhhAjNN0me7VGIb0X5hu3ogyi5DDPapjGAHdEffO5vi+HX1BFWLRD/Ximx5PjGAKjAqQ== +"@semantic-release/commit-analyzer@^9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz#a78e54f9834193b55f1073fa6258eecc9a545e03" + integrity sha512-E+dr6L+xIHZkX4zNMe6Rnwg4YQrWNXK+rNsvwOPpdFppvZO1olE2fIgWhv89TkQErygevbjsZFSIxp+u6w2e5g== dependencies: conventional-changelog-angular "^5.0.0" conventional-commits-filter "^2.0.0" - conventional-commits-parser "^3.0.7" + conventional-commits-parser "^3.2.3" debug "^4.0.0" import-from "^4.0.0" lodash "^4.17.4" micromatch "^4.0.2" -"@semantic-release/error@^2.1.0", "@semantic-release/error@^2.2.0": +"@semantic-release/error@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@semantic-release/error/-/error-2.2.0.tgz#ee9d5a09c9969eade1ec864776aeda5c5cddbbf0" integrity sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg== @@ -1522,12 +2777,12 @@ resolved "https://registry.yarnpkg.com/@semantic-release/error/-/error-3.0.0.tgz#30a3b97bbb5844d695eb22f9d3aa40f6a92770c2" integrity sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw== -"@semantic-release/exec@6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@semantic-release/exec/-/exec-6.0.1.tgz#6316f52ad786c7fafff5e1cbcfeeb7608501db78" - integrity sha512-RlMoxuhQ7QujrykIG5uw0NU6x82BR4E7ssKsl+ISCFhFHnvxxH+w4h4klWOs/cT/XEPJdoPFbBOVoruKtAwfDg== +"@semantic-release/exec@6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@semantic-release/exec/-/exec-6.0.2.tgz#1eba943170595192083e71c7788823c570443bbb" + integrity sha512-ciaqJTHB1TFtU6C78xrgmoNI9UyfheR9+Bk6Ico7CJ7+ADOEAvUrPBKvz64UCfoWlg+SlKGTVGbFnA509wRUVw== dependencies: - "@semantic-release/error" "^2.1.0" + "@semantic-release/error" "^3.0.0" aggregate-error "^3.0.0" debug "^4.0.0" execa "^5.0.0" @@ -1535,9 +2790,9 @@ parse-json "^5.0.0" "@semantic-release/github@^8.0.0": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@semantic-release/github/-/github-8.0.1.tgz#58d6bc5cdb4b4beaa0a106a9a9ad420318144e4f" - integrity sha512-T01lfh4yBZodAeo8t0U+W5hmPYR9BdnfwLDerXnGaYeLXm8+KMx4mQEBAf/UbRVlzmIKTqMx+/s9fY/mSQNV0A== + version "8.0.2" + resolved "https://registry.yarnpkg.com/@semantic-release/github/-/github-8.0.2.tgz#80114a41f6ec8ab6c0d38a436b48ff3f2223ab16" + integrity sha512-wIbfhOeuxlYzMTjtSAa2xgr54n7ZuPAS2gadyTWBpUt2PNAPgla7A6XxCXJnaKPgfVF0iFfSk3B+KlVKk6ByVg== dependencies: "@octokit/rest" "^18.0.0" "@semantic-release/error" "^2.2.0" @@ -1551,15 +2806,15 @@ https-proxy-agent "^5.0.0" issue-parser "^6.0.0" lodash "^4.17.4" - mime "^2.4.3" + mime "^3.0.0" p-filter "^2.0.0" p-retry "^4.0.0" url-join "^4.0.0" "@semantic-release/npm@^8.0.0": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@semantic-release/npm/-/npm-8.0.2.tgz#1dc285e4250380bc3259735bf843738c7151f275" - integrity sha512-ky7QczJGExZJnS8wjdhag1EV+T3u/DbcxqpBQOIZu1fcJYKsGxr2uK9guc/H2cg0yFCto5atbuZm2sZmaG5qxg== + version "8.0.3" + resolved "https://registry.yarnpkg.com/@semantic-release/npm/-/npm-8.0.3.tgz#69378ce529bbd263aa8fc899b2d0f874114e0302" + integrity sha512-Qbg7x/O1t3sJqsv2+U0AL4Utgi/ymlCiUdt67Ftz9HL9N8aDML4t2tE0T9MBaYdqwD976hz57DqHHXKVppUBoA== dependencies: "@semantic-release/error" "^3.0.0" aggregate-error "^3.0.0" @@ -1576,14 +2831,14 @@ tempy "^1.0.0" "@semantic-release/release-notes-generator@^10.0.0": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.2.tgz#944068c6ba0cf5d7779bdfeb537db29a4d295622" - integrity sha512-I4eavIcDan8fNQHskZ2cbWkFMimvgxNkqR2UfuYNwYBgswEl3SJsN8XMf9gZWObt6nXDc2QfDwhjy8DjTZqS3w== + version "10.0.3" + resolved "https://registry.yarnpkg.com/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz#85f7ca78bfa6b01fb5fda0ac48112855d69171dc" + integrity sha512-k4x4VhIKneOWoBGHkx0qZogNjCldLPRiAjnIpMnlUh6PtaWXp/T+C9U7/TaNDDtgDa5HMbHl4WlREdxHio6/3w== dependencies: conventional-changelog-angular "^5.0.0" conventional-changelog-writer "^5.0.0" conventional-commits-filter "^2.0.0" - conventional-commits-parser "^3.0.0" + conventional-commits-parser "^3.2.3" debug "^4.0.0" get-stream "^6.0.0" import-from "^4.0.0" @@ -1596,20 +2851,41 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.2.0.tgz#667bfc6186ae7c9e0b45a08960c551437176e1ca" integrity sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw== -"@sinonjs/commons@^1.7.0": +"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.3": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== dependencies: type-detect "4.0.8" +"@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0", "@sinonjs/fake-timers@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" + integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers@^8.0.1": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz#1c1c9a91419f804e59ae8df316a07dd1c3a76b94" - integrity sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew== + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/samsam@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" + integrity sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" @@ -1617,6 +2893,70 @@ dependencies: defer-to-connect "^2.0.0" +"@thi.ng/api@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@thi.ng/api/-/api-7.2.0.tgz#ed3d7c70aca157a8f53613f7359be7d2e31d6f18" + integrity sha512-4NcwHXxwPF/JgJG/jSFd9rjfQNguF0QrHvd6e+CEf4T0sFChqetW6ZmJ6/a2X+noDVntgulegA+Bx0HHzw+Tyw== + +"@thi.ng/arrays@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@thi.ng/arrays/-/arrays-1.0.3.tgz#f4d26184f6da3ff30beb2488a131e19f187fd920" + integrity sha512-ZUB27bdpTwcvxYJTlt/eWKrj98nWXo0lAUPwRwubk4GlH8rTKKkc7qZr9/4LCKPsNjnZdQqbBtNvNf3HjYxCzw== + dependencies: + "@thi.ng/api" "^7.2.0" + "@thi.ng/checks" "^2.9.11" + "@thi.ng/compare" "^1.3.34" + "@thi.ng/equiv" "^1.0.45" + "@thi.ng/errors" "^1.3.4" + "@thi.ng/random" "^2.4.8" + +"@thi.ng/checks@^2.9.11": + version "2.9.11" + resolved "https://registry.yarnpkg.com/@thi.ng/checks/-/checks-2.9.11.tgz#b7e4c78828f553613d2af025ce7ae3f9306f27bb" + integrity sha512-fBvWod32w24JlJsrrOdl+tlx+UNehCORi4rHaJ7l7HH+SEhD/lYTCXOBjwu9D/ztIUjMP5Q+n8cAqI5iPhbvAQ== + dependencies: + tslib "^2.3.1" + +"@thi.ng/compare@^1.3.34": + version "1.3.34" + resolved "https://registry.yarnpkg.com/@thi.ng/compare/-/compare-1.3.34.tgz#321e10780955b18a4bcf7876d0fe0323be9d7be8" + integrity sha512-E+UWhmo8l5yeHDuriPUsfrnk/Mj5kSDNRX7lPfv2zNdAQ7N8UDzc0IXu46U6EpqtCReo+2n5N8qzfD3TjerFRw== + dependencies: + "@thi.ng/api" "^7.2.0" + +"@thi.ng/equiv@^1.0.45": + version "1.0.45" + resolved "https://registry.yarnpkg.com/@thi.ng/equiv/-/equiv-1.0.45.tgz#02bf71a630939c8f61a2a7b9e83cca9569ead7f3" + integrity sha512-tdXaJfF0pFvT80Q7BOlhc7H7ja/RbVGzlGpE4LqjDWfXPPbLYwmq6EbQuHWeXuvT0qe+BsGnuO5UXAR5B8oGGQ== + +"@thi.ng/errors@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@thi.ng/errors/-/errors-1.3.4.tgz#8f7675f7a895a87f0f609d64db69bc04483e0b29" + integrity sha512-hTk71OPKnioN349sdj2DAoY+69eSerB3MN4Zwz6mosr1QFzIMkfkNOtBeC+Gm0yi0V0EY5LeBYFgqb3oXbtTbw== + +"@thi.ng/hex@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@thi.ng/hex/-/hex-1.0.4.tgz#5c5c86d3aef0422709b4aa8cfadd3b3f01a4a808" + integrity sha512-9ofIG4nXhEskGeOJthpi/9LXFIPrlZ/MmHpgLWa3wNqTVhODP/o++mu9jDKojHEpKvswkkFCE+mSVmMu8xo4mQ== + +"@thi.ng/random@^2.4.8": + version "2.4.8" + resolved "https://registry.yarnpkg.com/@thi.ng/random/-/random-2.4.8.tgz#428950e501c5a76907e3e6cb93985db07322012e" + integrity sha512-4JJB8zbaPxjlAp1kCqsBbs6eN4Ivd/5fs1e4GlvmNkyGSucHIDTWvw6NnQWqUx2oPaAEDB9CFCH7SOcGC/cwkw== + dependencies: + "@thi.ng/api" "^7.2.0" + "@thi.ng/checks" "^2.9.11" + "@thi.ng/hex" "^1.0.4" + +"@thi.ng/zipper@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@thi.ng/zipper/-/zipper-1.0.3.tgz#705ce30a4391eac658d3d4ce6ffdd75919f72bbc" + integrity sha512-dWfuk5nzf5wGEmcF90AXNEuWr3NVwRF+cf/9ZSE6xImA7Vy5XpHNMwLHFszZaC+kqiDXr+EZ0lXWDF46a8lSPA== + dependencies: + "@thi.ng/api" "^7.2.0" + "@thi.ng/arrays" "^1.0.3" + "@thi.ng/checks" "^2.9.11" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -1685,10 +3025,10 @@ dependencies: "@babel/types" "^7.3.0" -"@types/bunyan@1.8.7": - version "1.8.7" - resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.7.tgz#63cc65b5ecff6217d1509409a575e7b991f80831" - integrity sha512-jaNt6xX5poSmXuDAkQrSqx2zkR66OrdRDuVnU8ldvn3k/Ci/7Sf5nooKspQWimDnw337Bzt/yirqSThTjvrHkg== +"@types/bunyan@1.8.8": + version "1.8.8" + resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.8.tgz#8d6d33f090f37c07e2a80af30ae728450a101008" + integrity sha512-Cblq+Yydg3u+sGiz2mjHjC5MPmdjY+No4qvHrF+BUhblsmSfMvsHLbOG62tPbonsqBj6sbWv1LHcsoe5Jw+/Ow== dependencies: "@types/node" "*" @@ -1725,14 +3065,14 @@ integrity sha512-ZB+G1yG5pQoWaaHg0D4H9c29q61Y0O/kyOtEx0IKL3Gca3EGEcN3jh5OHnoQp00/pFoqJR63htsviivOkTXuCw== "@types/emscripten@^1.38.0": - version "1.39.5" - resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.5.tgz#eb0fb1048301df980b6f8a5ec3d63f7d1572bb73" - integrity sha512-DIOOg+POSrYl+OlNRHQuIEqCd8DCtynG57H862UCce16nXJX7J8eWxNGgOcf8Eyge8zXeSs27mz1UcFu8L/L7g== + version "1.39.6" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.6.tgz#698b90fe60d44acf93c31064218fbea93fbfd85a" + integrity sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg== -"@types/eslint@7.28.2": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.2.tgz#0ff2947cdd305897c52d5372294e8c76f351db68" - integrity sha512-KubbADPkfoU75KgKeKLsFHXnU4ipH7wYg0TRT33NK3N3yiu7jlFAAoygIWBV+KbuHx/G+AvuGX6DllnK35gfJA== +"@types/eslint@7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -1808,25 +3148,25 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@27.0.2": - version "27.0.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7" - integrity sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA== +"@types/jest@27.0.3": + version "27.0.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.3.tgz#0cf9dfe9009e467f70a342f0f94ead19842a783a" + integrity sha512-cmmwv9t7gBYt7hNKH5Spu7Kuu/DotGa+Ff+JGRKZ4db5eh8PnKS4LuebJ3YLUoyOyIHraTGyULn23YtEAm0VSg== dependencies: jest-diff "^27.0.0" pretty-format "^27.0.0" -"@types/js-yaml@4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.4.tgz#cc38781257612581a1a0eb25f1709d2b06812fce" - integrity sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ== +"@types/js-yaml@4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== "@types/json-dup-key-validator@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/json-dup-key-validator/-/json-dup-key-validator-1.0.0.tgz#3a666ab980e5e957960e9341e13eabd4fd9a24f3" integrity sha512-D/pvJeKintUSW4G+aBRSvReECxCHbMcE9IuSU7cfWQAtYStE/vH8OLXFw38JJ37dSEnpcOkEV7q4WmdvtV0zHg== -"@types/json-schema@*", "@types/json-schema@^7.0.7": +"@types/json-schema@*", "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== @@ -1853,10 +3193,10 @@ resolved "https://registry.yarnpkg.com/@types/linkify-markdown/-/linkify-markdown-1.0.1.tgz#0b750a592107dd46ecf2b5be0eeb7656b1fc814d" integrity sha512-RYDOtCol7/sHGhSJvWVnl0AmOdQQWgUYys6cwn5Lt3RiYhyhTLMLv7B9wdixMgCfnNt0MQj/YSGi3qN0IQqLeQ== -"@types/luxon@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-2.0.5.tgz#29d3b095d55ee50df8f4cf109b16009334d9828e" - integrity sha512-GKrG5v16BOs9XGpouu33hOkAFaiSDi3ZaDXG9F2yAoyzHRBtksZnI60VWY5aM/yAENCccBejrxw8jDY+9OVlxw== +"@types/luxon@2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-2.0.7.tgz#6189930542400e2c48b4d5ed06c4f136ee38bb1a" + integrity sha512-AxiYycfO+/M4VIH0ribSr2iPFC+APewpJIaQSydwVnzorK3mjSFXkA3HmhQidGx44MpwaatFyEkbW/WD4zdDaQ== "@types/markdown-it@12.2.3": version "12.2.3" @@ -1918,14 +3258,14 @@ integrity sha512-0fRfA90FWm6KJfw6P9QGyo0HDTCmthZ7cWaBQndITlaWLTZ6njRyKwrwpzpg+n6kBXBIGKeUHEQuBx7bphGJkA== "@types/node@*": - version "16.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" - integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + version "16.11.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.11.tgz#6ea7342dfb379ea1210835bada87b3c512120234" + integrity sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw== -"@types/node@14.17.32": - version "14.17.32" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.32.tgz#2ca61c9ef8c77f6fa1733be9e623ceb0d372ad96" - integrity sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ== +"@types/node@14.17.34": + version "14.17.34" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.34.tgz#fe4b38b3f07617c0fa31ae923fca9249641038f0" + integrity sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg== "@types/node@^13.7.0": version "13.13.52" @@ -1942,15 +3282,15 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/parse-link-header@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.0.tgz#69f059e40a0fa93dc2e095d4142395ae6adc5d7a" - integrity sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA== +"@types/parse-link-header@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.1.tgz#6eade790736a050b9242c7c6b2fceda9b3dc394a" + integrity sha512-E2+Go9rQgPbmpkeA2iFXTWSTxX38KXlXwcdiIbt71Oorqr+G5QtH4AhpuDdxwRVyiTzdUrHnaaIumW/LhiZwVg== "@types/prettier@^2.1.5": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" - integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281" + integrity sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA== "@types/redis@^2.8.30": version "2.8.32" @@ -1999,6 +3339,13 @@ "@types/glob" "*" "@types/node" "*" +"@types/sinon@10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.2.tgz#f360d2f189c0fd433d14aeb97b9d705d7e4cc0e4" + integrity sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw== + dependencies: + "@sinonjs/fake-timers" "^7.1.0" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -2055,75 +3402,75 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" - integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== +"@typescript-eslint/eslint-plugin@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz#12d5f47f127af089b985f3a205c0e34a812f8fce" + integrity sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA== dependencies: - "@typescript-eslint/experimental-utils" "4.33.0" - "@typescript-eslint/scope-manager" "4.33.0" - debug "^4.3.1" + "@typescript-eslint/experimental-utils" "5.5.0" + "@typescript-eslint/scope-manager" "5.5.0" + debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" - regexpp "^3.1.0" + regexpp "^3.2.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.33.0", "@typescript-eslint/experimental-utils@^4.0.1": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" - integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== +"@typescript-eslint/experimental-utils@5.5.0", "@typescript-eslint/experimental-utils@^5.0.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz#3fe2514dc2f3cd95562206e4058435ea51df609e" + integrity sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A== dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.5.0" + "@typescript-eslint/types" "5.5.0" + "@typescript-eslint/typescript-estree" "5.5.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@4.33.0", "@typescript-eslint/parser@^4.4.1": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" - integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== +"@typescript-eslint/parser@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.5.0.tgz#a38070e225330b771074daa659118238793f7fcd" + integrity sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg== dependencies: - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - debug "^4.3.1" + "@typescript-eslint/scope-manager" "5.5.0" + "@typescript-eslint/types" "5.5.0" + "@typescript-eslint/typescript-estree" "5.5.0" + debug "^4.3.2" -"@typescript-eslint/scope-manager@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" - integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== +"@typescript-eslint/scope-manager@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz#2b9f3672fa6cddcb4160e7e8b49ef1fd00f83c09" + integrity sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg== dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" + "@typescript-eslint/types" "5.5.0" + "@typescript-eslint/visitor-keys" "5.5.0" -"@typescript-eslint/types@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" - integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== +"@typescript-eslint/types@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.5.0.tgz#fee61ae510e84ed950a53937a2b443e078107003" + integrity sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg== -"@typescript-eslint/typescript-estree@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" - integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== +"@typescript-eslint/typescript-estree@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz#12f422698c1636bd0206086bbec9844c54625ebc" + integrity sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ== dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" + "@typescript-eslint/types" "5.5.0" + "@typescript-eslint/visitor-keys" "5.5.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" - integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== +"@typescript-eslint/visitor-keys@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz#4787586897b61f26068a3db5c50b3f5d254f9083" + integrity sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw== dependencies: - "@typescript-eslint/types" "4.33.0" - eslint-visitor-keys "^2.0.0" + "@typescript-eslint/types" "5.5.0" + eslint-visitor-keys "^3.0.0" "@yarnpkg/core@2.4.0": version "2.4.0" @@ -2258,15 +3605,15 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.2.4, acorn@^8.4.1, acorn@^8.6.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" + integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -2302,16 +3649,6 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.6.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.3.tgz#11a66527761dc3e9a3845ea775d2d3c0414e8764" - integrity sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -2329,11 +3666,6 @@ ansi-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -2495,9 +3827,9 @@ asn1.js@^5.0.0: safer-buffer "^2.1.0" asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" @@ -2506,11 +3838,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2521,6 +3848,15 @@ auth-header@1.0.0: resolved "https://registry.yarnpkg.com/auth-header/-/auth-header-1.0.0.tgz#ea24fdc5588e1eb8b750df8655a396aa48fc9076" integrity sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA== +aws-sdk-client-mock@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/aws-sdk-client-mock/-/aws-sdk-client-mock-0.5.6.tgz#73c44356aa49f24467e52dfeef4b6b33c164d91d" + integrity sha512-67C+6vlSMPhVGaDlUak3XVR/qvah4ENMMJMl+aWVnK42pBznQQuNvYzlg+OBeW+aBa6kOVDSAYstIqNfInIB/A== + dependencies: + "@types/sinon" "10.0.2" + sinon "^11.1.1" + tslib "^2.1.0" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2531,24 +3867,24 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -azure-devops-node-api@11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-11.0.1.tgz#b7ec4783230e1de8fc972b23effe7ed2ebac17ff" - integrity sha512-YMdjAw9l5p/6leiyIloxj3k7VIvYThKjvqgiQn88r3nhT93ENwsoDS3A83CyJ4uTWzCZ5f5jCi6c27rTU5Pz+A== +azure-devops-node-api@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-11.1.0.tgz#ea3ca49de8583b0366d000f3c3f8a75b8104055f" + integrity sha512-6/2YZuf+lJzJLrjXNYEA5RXAkMCb8j/4VcHD0qJQRsgG/KsRMYo0HgDh0by1FGHyZkQWY5LmQyJqCwRVUB3Y7Q== dependencies: tunnel "0.0.6" typed-rest-client "^1.8.4" -babel-jest@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.3.1.tgz#0636a3404c68e07001e434ac4956d82da8a80022" - integrity sha512-SjIF8hh/ir0peae2D6S6ZKRhUy7q/DnpH7k/V6fT4Bgs/LXXUztOpX4G2tCgq8mLo5HA9mN6NmlFMeYtKmIsTQ== +babel-jest@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.4.2.tgz#6edf80971045cfd44f3f10b6eda6007d95f62742" + integrity sha512-MADrjb3KBO2eyZCAc6QaJg6RT5u+6oEdDyHO5HEalnpwQ6LrhTsQF2Kj1Wnz2t6UPXIXPk18dSXXOT0wF5yTxA== dependencies: - "@jest/transform" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/transform" "^27.4.2" + "@jest/types" "^27.4.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^27.2.0" + babel-preset-jest "^27.4.0" chalk "^4.0.0" graceful-fs "^4.2.4" slash "^3.0.0" @@ -2564,10 +3900,10 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^27.2.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz#79f37d43f7e5c4fdc4b2ca3e10cc6cf545626277" - integrity sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw== +babel-plugin-jest-hoist@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz#d7831fc0f93573788d80dee7e682482da4c730d6" + integrity sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -2592,12 +3928,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^27.2.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz#556bbbf340608fed5670ab0ea0c8ef2449fba885" - integrity sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg== +babel-preset-jest@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz#70d0e676a282ccb200fbabd7f415db5fdf393bca" + integrity sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg== dependencies: - babel-plugin-jest-hoist "^27.2.0" + babel-plugin-jest-hoist "^27.4.0" babel-preset-current-node-syntax "^1.0.0" backslash@^0.2.0: @@ -2708,13 +4044,13 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.16.6: - version "4.17.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.5.tgz#c827bbe172a4c22b123f5e337533ceebadfdd559" - integrity sha512-I3ekeB92mmpctWBoLXe0d5wPS2cBuRvvW0JyyJHMrk9/HmP2ZjrTboNAZ8iuGqaEIlKguljbQY32OkOJIRrgoA== +browserslist@^4.17.5: + version "4.18.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" + integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ== dependencies: - caniuse-lite "^1.0.30001271" - electron-to-chromium "^1.3.878" + caniuse-lite "^1.0.30001280" + electron-to-chromium "^1.3.896" escalade "^3.1.1" node-releases "^2.0.1" picocolors "^1.0.0" @@ -2795,7 +4131,7 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== -cacheable-request@^7.0.1: +cacheable-request@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== @@ -2836,14 +4172,14 @@ camelcase@^5.0.0, camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + version "6.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" + integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== -caniuse-lite@^1.0.30001271: - version "1.0.30001274" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001274.tgz#26ca36204d15b17601ba6fc35dbdad950a647cc7" - integrity sha512-+Nkvv0fHyhISkiMIjnyjmf5YJcQ1IQHZN6U9TLUMroWR38FNwpsC51Gb68yueafX1V6ifOisInSgP9WJFS13ew== +caniuse-lite@^1.0.30001280: + version "1.0.30001284" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001284.tgz#d3653929ded898cd0c1f09a56fd8ca6952df4fca" + integrity sha512-t28SKa7g6kiIQi6NHeOcKrOrGMzCRrXvlasPwWC26TH2QNdglgzQIRUuJ0cR3NeQPH+5jpuveeeSFDLm2zbkEw== cardinal@^2.1.1: version "2.1.1" @@ -2858,7 +4194,12 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@*, chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0: +chalk@*: + version "5.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.0.tgz#bd96c6bb8e02b96e08c0c3ee2a9d90e050c7b832" + integrity sha512-/duVOqst+luxCQRKEo4bNxinsOQtMP80ZYm7mMqzuh5PociNL0PvmHFvREJ9ueYL2TxlHjBcmLCdmocx9Vg+IQ== + +chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2919,9 +4260,9 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" - integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== cidr-regex@^3.1.1: version "3.1.1" @@ -3088,11 +4429,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -confusing-browser-globals@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" - integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== - console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -3148,7 +4484,7 @@ conventional-commits-filter@^2.0.0, conventional-commits-filter@^2.0.7: lodash.ismatch "^4.4.0" modify-values "^1.0.0" -conventional-commits-parser@^3.0.0, conventional-commits-parser@^3.0.7: +conventional-commits-parser@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.3.tgz#fc43704698239451e3ef35fd1d8ed644f46bd86e" integrity sha512-YyRDR7On9H07ICFpRm/igcdjIqebXbvf4Cff+Pf0BrBys1i1EOzx9iFXNlAbdrLAR8jf7bkUYkDAr8pEy0q4Pw== @@ -3168,14 +4504,14 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: safe-buffer "~5.1.1" core-js-pure@^3.19.0: - version "3.19.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.19.0.tgz#db6fdadfdd4dc280ec93b64c3c2e8460e6f10094" - integrity sha512-UEQk8AxyCYvNAs6baNoPqDADv7BX0AmBLGxVsrAifPPx/C8EAzV4Q+2ZUJqVzfI2TQQEZITnwUkWcHpgc/IubQ== + version "3.19.2" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.19.2.tgz#26b5bfb503178cff6e3e115bc2ba6c6419383680" + integrity sha512-5LkcgQEy8pFeVnd/zomkUBSwnmIxuF1C8E9KrMAbOc8f34IBT9RGvTYeNDdp1PnvMJrrVhvk1hg/yVV5h/znlg== core-js@^3.6.5: - version "3.19.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.0.tgz#9e40098a9bc326c7e81b486abbd5e12b9d275176" - integrity sha512-L1TpFRWXZ76vH1yLM+z6KssLZrP8Z6GxxW4auoCj+XiViOzNPJCAuTIkn03BGdFe6Z5clX5t64wRIRypsZQrUg== + version "3.19.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.2.tgz#ae216d7f4f7e924d9a2e3ff1e4b1940220f9157b" + integrity sha512-ciYCResnLIATSsXuXnIOH4CbdfgV+H1Ltg16hJFN7/v6OxqnFr/IFGeLacaZ+fHLAm0TBbXwNK9/DNBzBUrO/g== core-util-is@1.0.2: version "1.0.2" @@ -3253,7 +4589,7 @@ css-select@^4.1.3: domutils "^2.6.0" nth-check "^2.0.0" -css-what@^5.0.0, css-what@^5.0.1: +css-what@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== @@ -3301,10 +4637,10 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -3374,6 +4710,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-freeze-es6@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/deep-freeze-es6/-/deep-freeze-es6-1.4.1.tgz#20511df80b70e1a1b92ac3102d8ccd11363df16a" + integrity sha512-WnyM4ZCzpHtziy7LxnpQPGS+mfZDQvFpmH3LGqmyUfzwBLkvNTwbn+mTwSNTQoLQTHiN85qZqprRZdYwU3xN/Q== + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -3480,10 +4821,10 @@ diff-sequences@^26.6.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== -diff-sequences@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" - integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== +diff-sequences@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5" + integrity sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww== diff@^4.0.1: version "4.0.2" @@ -3538,9 +4879,9 @@ domexception@^2.0.1: webidl-conversions "^5.0.0" domhandler@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" - integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== dependencies: domelementtype "^2.2.0" @@ -3592,10 +4933,10 @@ editorconfig@0.15.3: semver "^5.6.0" sigmund "^1.0.1" -electron-to-chromium@^1.3.878: - version "1.3.885" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.885.tgz#c8cec32fbc61364127849ae00f2395a1bae7c454" - integrity sha512-JXKFJcVWrdHa09n4CNZYfYaK6EW5aAew7/wr3L1OnsD1L+JHL+RCtd7QgIsxUbFPeTwPlvnpqNNTOLkoefmtXg== +electron-to-chromium@^1.3.896: + version "1.4.11" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.11.tgz#303c9deebbe90c68bf5c2c81a88a3bf4522c8810" + integrity sha512-2OhsaYgsWGhWjx2et8kaUcdktPbBGjKM2X0BReUCKcSCPttEY+hz2zie820JLbttU8jwL92+JJysWwkut3wZgA== email-addresses@5.0.0: version "5.0.0" @@ -3671,9 +5012,9 @@ entities@~2.1.0: integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== env-ci@^5.0.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-5.4.1.tgz#814387ddd6857b37472ef612361f34d720c29a18" - integrity sha512-xyuCtyFZLpnW5aH0JstETKTSMwHHQX4m42juzEZzvbUCJX7RiPVlhASKM0f/cJ4vvI/+txMkZ7F5To6dCdPYhg== + version "5.5.0" + resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-5.5.0.tgz#43364e3554d261a586dec707bc32be81112b545f" + integrity sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A== dependencies: execa "^5.0.0" fromentries "^1.3.2" @@ -3768,33 +5109,6 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-config-airbnb-base@^14.2.0, eslint-config-airbnb-base@^14.2.1: - version "14.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" - integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== - dependencies: - confusing-browser-globals "^1.0.10" - object.assign "^4.1.2" - object.entries "^1.1.2" - -eslint-config-airbnb-typescript@12.3.1: - version "12.3.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-12.3.1.tgz#83ab40d76402c208eb08516260d1d6fac8f8acbc" - integrity sha512-ql/Pe6/hppYuRp4m3iPaHJqkBB7dgeEmGPQ6X0UNmrQOfTF+dXw29/ZjU2kQ6RDoLxaxOA+Xqv07Vbef6oVTWw== - dependencies: - "@typescript-eslint/parser" "^4.4.1" - eslint-config-airbnb "^18.2.0" - eslint-config-airbnb-base "^14.2.0" - -eslint-config-airbnb@^18.2.0: - version "18.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz#b7fe2b42f9f8173e825b73c8014b592e449c98d9" - integrity sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg== - dependencies: - eslint-config-airbnb-base "^14.2.1" - object.assign "^4.1.2" - object.entries "^1.1.2" - eslint-config-prettier@8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" @@ -3831,7 +5145,7 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.0: +eslint-module-utils@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== @@ -3840,31 +5154,31 @@ eslint-module-utils@^2.7.0: find-up "^2.1.0" pkg-dir "^2.0.0" -eslint-plugin-import@2.25.2: - version "2.25.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz#b3b9160efddb702fc1636659e71ba1d10adbe9e9" - integrity sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g== +eslint-plugin-import@2.25.3: + version "2.25.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" + integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.0" + eslint-module-utils "^2.7.1" has "^1.0.3" - is-core-module "^2.7.0" + is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" tsconfig-paths "^3.11.0" -eslint-plugin-jest@24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz#206ac0833841e59e375170b15f8d0955219c4889" - integrity sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA== +eslint-plugin-jest@25.3.0: + version "25.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.3.0.tgz#6c04bbf13624a75684a05391a825b58e2e291950" + integrity sha512-79WQtuBsTN1S8Y9+7euBYwxIOia/k7ykkl9OCBHL3xuww5ecursHy/D8GCIlvzHVWv85gOkS5Kv6Sh7RxOgK1Q== dependencies: - "@typescript-eslint/experimental-utils" "^4.0.1" + "@typescript-eslint/experimental-utils" "^5.0.0" eslint-plugin-promise@5.1.1: version "5.1.1" @@ -3879,12 +5193,13 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== dependencies: - eslint-visitor-keys "^1.1.0" + esrecurse "^4.3.0" + estraverse "^5.2.0" eslint-utils@^3.0.0: version "3.0.0" @@ -3893,47 +5208,46 @@ eslint-utils@^3.0.0: dependencies: eslint-visitor-keys "^2.0.0" -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - eslint-visitor-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@7.32.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== +eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" + integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== + +eslint@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.3.0.tgz#a3c2409507403c1c7f6c42926111d6cbefbc3e85" + integrity sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww== dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" + "@eslint/eslintrc" "^1.0.4" + "@humanwhocodes/config-array" "^0.6.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" + eslint-scope "^7.1.0" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.1.0" + espree "^9.1.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" + glob-parent "^6.0.1" globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" @@ -3941,22 +5255,21 @@ eslint@7.32.0: natural-compare "^1.4.0" optionator "^0.9.1" progress "^2.0.0" - regexpp "^3.1.0" + regexpp "^3.2.0" semver "^7.2.1" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" strip-json-comments "^3.1.0" - table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.1.0, espree@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.2.0.tgz#c50814e01611c2d0f8bd4daa83c369eabba80dbc" + integrity sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg== dependencies: - acorn "^7.4.0" + acorn "^8.6.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + eslint-visitor-keys "^3.1.0" esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" @@ -4070,17 +5383,17 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" -expect@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.3.1.tgz#d0f170b1f5c8a2009bab0beffd4bb94f043e38e7" - integrity sha512-MrNXV2sL9iDRebWPGOGFdPQRl2eDQNu/uhxIMShjjx74T6kC6jFIkmQ6OqXDtevjGUkyB2IT56RzDBqXf/QPCg== +expect@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.4.2.tgz#4429b0f7e307771d176de9bdf23229b101db6ef6" + integrity sha512-BjAXIDC6ZOW+WBFNg96J22D27Nq5ohn+oGcuP2rtOtcjuxNoV9McpQ60PcQWhdFOSBIQdR72e+4HdnbZTFSTyg== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" ansi-styles "^5.0.0" - jest-get-type "^27.3.1" - jest-matcher-utils "^27.3.1" - jest-message-util "^27.3.1" - jest-regex-util "^27.0.6" + jest-get-type "^27.4.0" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-regex-util "^27.4.0" extend@^3.0.0, extend@~3.0.2: version "3.0.2" @@ -4104,9 +5417,9 @@ extsprintf@1.3.0: integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -4242,9 +5555,9 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.1.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" - integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== + version "3.2.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== forever-agent@~0.6.1: version "0.6.1" @@ -4323,19 +5636,19 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.1.tgz#4bea07bcde3782f06dced8950e51307aa0f4a346" - integrity sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ== +gauge@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.0.tgz#afba07aa0374a93c6219603b1fb83eaa2264d8f8" + integrity sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw== dependencies: + ansi-regex "^5.0.1" aproba "^1.0.3 || ^2.0.0" color-support "^1.1.2" console-control-strings "^1.0.0" has-unicode "^2.0.1" - object-assign "^4.1.1" signal-exit "^3.0.0" - string-width "^1.0.1 || ^2.0.0" - strip-ansi "^3.0.1 || ^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" wide-align "^1.1.2" gauge@~2.7.3: @@ -4453,6 +5766,13 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob@*, glob@7.2.0, glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -4508,7 +5828,7 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" -globby@^11.0.0, globby@^11.0.1, globby@^11.0.3, globby@~11.0.4: +globby@^11.0.0, globby@^11.0.1, globby@^11.0.4, globby@~11.0.4: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== @@ -4520,17 +5840,17 @@ globby@^11.0.0, globby@^11.0.1, globby@^11.0.3, globby@~11.0.4: merge2 "^1.3.0" slash "^3.0.0" -got@11.8.2, got@^11.7.0: - version "11.8.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" - integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== +got@11.8.3, got@^11.7.0: + version "11.8.3" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" + integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" + cacheable-request "^7.0.2" decompress-response "^6.0.0" http2-wrapper "^1.0.0-beta.5.2" lowercase-keys "^2.0.0" @@ -4773,10 +6093,10 @@ ignore-walk@^4.0.1: dependencies: minimatch "^3.0.4" -ignore@5.1.8, ignore@^5.1.4, ignore@^5.1.8: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@5.1.9, ignore@^5.1.4, ignore@^5.1.8: + version "5.1.9" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" + integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== ignore@^4.0.6: version "4.0.6" @@ -4949,7 +6269,7 @@ is-cidr@*: dependencies: cidr-regex "^3.1.1" -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.7.0: +is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== @@ -4980,11 +6300,6 @@ is-fullwidth-code-point@^1.0.0: dependencies: number-is-nan "^1.0.0" -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -5127,6 +6442,11 @@ is@^3.2.1: resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -5198,9 +6518,9 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" - integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== + version "3.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.1.tgz#7085857f17d2441053c6ce5c3b8fdf6882289397" + integrity sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -5210,84 +6530,85 @@ java-properties@^1.0.0: resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211" integrity sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ== -jest-changed-files@^27.3.0: - version "27.3.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.3.0.tgz#22a02cc2b34583fc66e443171dc271c0529d263c" - integrity sha512-9DJs9garMHv4RhylUMZgbdCJ3+jHSkpL9aaVKp13xtXAD80qLTLrqcDZL1PHA9dYA0bCI86Nv2BhkLpLhrBcPg== +jest-changed-files@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.4.2.tgz#da2547ea47c6e6a5f6ed336151bd2075736eb4a5" + integrity sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" execa "^5.0.0" throat "^6.0.1" -jest-circus@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.3.1.tgz#1679e74387cbbf0c6a8b42de963250a6469e0797" - integrity sha512-v1dsM9II6gvXokgqq6Yh2jHCpfg7ZqV4jWY66u7npz24JnhP3NHxI0sKT7+ZMQ7IrOWHYAaeEllOySbDbWsiXw== +jest-circus@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.4.2.tgz#466f482207ca9f323b78416c28f4d1fa7588159a" + integrity sha512-2ePUSru1BGMyzxsMvRfu+tNb+PW60rUyMLJBfw1Nrh5zC8RoTPfF+zbE0JToU31a6ZVe4nnrNKWYRzlghAbL0A== dependencies: - "@jest/environment" "^27.3.1" - "@jest/test-result" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/environment" "^27.4.2" + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" - expect "^27.3.1" + expect "^27.4.2" is-generator-fn "^2.0.0" - jest-each "^27.3.1" - jest-matcher-utils "^27.3.1" - jest-message-util "^27.3.1" - jest-runtime "^27.3.1" - jest-snapshot "^27.3.1" - jest-util "^27.3.1" - pretty-format "^27.3.1" + jest-each "^27.4.2" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-runtime "^27.4.2" + jest-snapshot "^27.4.2" + jest-util "^27.4.2" + pretty-format "^27.4.2" slash "^3.0.0" stack-utils "^2.0.3" throat "^6.0.1" -jest-cli@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.3.1.tgz#b576f9d146ba6643ce0a162d782b40152b6b1d16" - integrity sha512-WHnCqpfK+6EvT62me6WVs8NhtbjAS4/6vZJnk7/2+oOr50cwAzG4Wxt6RXX0hu6m1169ZGMlhYYUNeKBXCph/Q== +jest-cli@^27.4.2: + version "27.4.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.4.3.tgz#89acba683b9f91c7a5e342e2ea13aa5414836a0d" + integrity sha512-zZSJBXNC/i8UnJPwcKWsqnhGgIF3uoTYP7th32Zej7KNQJdxzOMj+wCfy2Ox3kU7nXErJ36DtYyXDhfiqaiDRw== dependencies: - "@jest/core" "^27.3.1" - "@jest/test-result" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/core" "^27.4.3" + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" import-local "^3.0.2" - jest-config "^27.3.1" - jest-util "^27.3.1" - jest-validate "^27.3.1" + jest-config "^27.4.3" + jest-util "^27.4.2" + jest-validate "^27.4.2" prompts "^2.0.1" yargs "^16.2.0" -jest-config@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.3.1.tgz#cb3b7f6aaa8c0a7daad4f2b9573899ca7e09bbad" - integrity sha512-KY8xOIbIACZ/vdYCKSopL44I0xboxC751IX+DXL2+Wx6DKNycyEfV3rryC3BPm5Uq/BBqDoMrKuqLEUNJmMKKg== +jest-config@^27.4.3: + version "27.4.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.4.3.tgz#7820e08f7526fa3f725423e2f0fa7888ee0ef9c9" + integrity sha512-DQ10HTSqYtC2pO7s9j2jw+li4xUnm2wLYWH2o7K1ftB8NyvToHsXoLlXxtsGh3AW9gUQR6KY/4B7G+T/NswJBw== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^27.3.1" - "@jest/types" "^27.2.5" - babel-jest "^27.3.1" + "@jest/test-sequencer" "^27.4.2" + "@jest/types" "^27.4.2" + babel-jest "^27.4.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.1" graceful-fs "^4.2.4" - jest-circus "^27.3.1" - jest-environment-jsdom "^27.3.1" - jest-environment-node "^27.3.1" - jest-get-type "^27.3.1" - jest-jasmine2 "^27.3.1" - jest-regex-util "^27.0.6" - jest-resolve "^27.3.1" - jest-runner "^27.3.1" - jest-util "^27.3.1" - jest-validate "^27.3.1" + jest-circus "^27.4.2" + jest-environment-jsdom "^27.4.3" + jest-environment-node "^27.4.2" + jest-get-type "^27.4.0" + jest-jasmine2 "^27.4.2" + jest-regex-util "^27.4.0" + jest-resolve "^27.4.2" + jest-runner "^27.4.3" + jest-util "^27.4.2" + jest-validate "^27.4.2" micromatch "^4.0.4" - pretty-format "^27.3.1" + pretty-format "^27.4.2" + slash "^3.0.0" jest-diff@^26.6.2: version "26.6.2" @@ -5299,63 +6620,63 @@ jest-diff@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-diff@^27.0.0, jest-diff@^27.0.6, jest-diff@^27.2.5, jest-diff@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.3.1.tgz#d2775fea15411f5f5aeda2a5e02c2f36440f6d55" - integrity sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ== +jest-diff@^27.0.0, jest-diff@^27.0.6, jest-diff@^27.2.5, jest-diff@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.2.tgz#786b2a5211d854f848e2dcc1e324448e9481f36f" + integrity sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q== dependencies: chalk "^4.0.0" - diff-sequences "^27.0.6" - jest-get-type "^27.3.1" - pretty-format "^27.3.1" + diff-sequences "^27.4.0" + jest-get-type "^27.4.0" + pretty-format "^27.4.2" -jest-docblock@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" - integrity sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA== +jest-docblock@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.4.0.tgz#06c78035ca93cbbb84faf8fce64deae79a59f69f" + integrity sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg== dependencies: detect-newline "^3.0.0" -jest-each@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.3.1.tgz#14c56bb4f18dd18dc6bdd853919b5f16a17761ff" - integrity sha512-E4SwfzKJWYcvOYCjOxhZcxwL+AY0uFMvdCOwvzgutJiaiodFjkxQQDxHm8FQBeTqDnSmKsQWn7ldMRzTn2zJaQ== +jest-each@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.4.2.tgz#19364c82a692d0d26557642098d1f4619c9ee7d3" + integrity sha512-53V2MNyW28CTruB3lXaHNk6PkiIFuzdOC9gR3C6j8YE/ACfrPnz+slB0s17AgU1TtxNzLuHyvNlLJ+8QYw9nBg== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" chalk "^4.0.0" - jest-get-type "^27.3.1" - jest-util "^27.3.1" - pretty-format "^27.3.1" - -jest-environment-jsdom@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.3.1.tgz#63ac36d68f7a9303494df783494856222b57f73e" - integrity sha512-3MOy8qMzIkQlfb3W1TfrD7uZHj+xx8Olix5vMENkj5djPmRqndMaXtpnaZkxmxM+Qc3lo+yVzJjzuXbCcZjAlg== - dependencies: - "@jest/environment" "^27.3.1" - "@jest/fake-timers" "^27.3.1" - "@jest/types" "^27.2.5" + jest-get-type "^27.4.0" + jest-util "^27.4.2" + pretty-format "^27.4.2" + +jest-environment-jsdom@^27.4.3: + version "27.4.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.4.3.tgz#74198285f6284888ca9c7486c4e5e67add75aa53" + integrity sha512-x1AUVz3G14LpEJs7KIFUaTINT2n0unOUmvdAby3s/sldUpJJetOJifHo1O/EUQC5fNBowggwJbVulko18y6OWw== + dependencies: + "@jest/environment" "^27.4.2" + "@jest/fake-timers" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" - jest-mock "^27.3.0" - jest-util "^27.3.1" + jest-mock "^27.4.2" + jest-util "^27.4.2" jsdom "^16.6.0" -jest-environment-node@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.3.1.tgz#af7d0eed04edafb740311b303f3fe7c8c27014bb" - integrity sha512-T89F/FgkE8waqrTSA7/ydMkcc52uYPgZZ6q8OaZgyiZkJb5QNNCF6oPZjH9IfPFfcc9uBWh1574N0kY0pSvTXw== +jest-environment-node@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.4.2.tgz#bf5586a0924a8d21c13838121ac0941638c7d15e" + integrity sha512-nzTZ5nJ+FabuZPH2YVci7SZIHpvtNRHPt8+vipLkCnAgXGjVzHm7XJWdnNqXbAkExIgiKeVEkVMNZOZE/LeiIg== dependencies: - "@jest/environment" "^27.3.1" - "@jest/fake-timers" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/environment" "^27.4.2" + "@jest/fake-timers" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" - jest-mock "^27.3.0" - jest-util "^27.3.1" + jest-mock "^27.4.2" + jest-util "^27.4.2" -jest-extended@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jest-extended/-/jest-extended-1.1.0.tgz#85b6f33c3ca53c65087b34663d4118927e728db8" - integrity sha512-rC1U9N4SYSFEiHJP3H0ZSbVAARwnjzBOFoluuFYePATsOJaX7PMMNiM9svS4WTgSNCIIVT3RWlWy+Y4W3zLsjg== +jest-extended@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/jest-extended/-/jest-extended-1.2.0.tgz#6ef87d806bd9501a0aeae56fe8c7af73777987ed" + integrity sha512-KYc5DgD+/8viJSEKBzb1vRXe/rEEQUxEovBTdNEer9A6lzvHvhuyslM5tQFBz8TbLEkicCmsEcQF+4N7GiPTLg== dependencies: expect "^26.6.2" jest-diff "^27.2.5" @@ -5367,10 +6688,10 @@ jest-get-type@^26.3.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-get-type@^27.0.6, jest-get-type@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.3.1.tgz#a8a2b0a12b50169773099eee60a0e6dd11423eff" - integrity sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg== +jest-get-type@^27.0.6, jest-get-type@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.4.0.tgz#7503d2663fffa431638337b3998d39c5e928e9b5" + integrity sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ== jest-github-actions-reporter@1.0.3: version "1.0.3" @@ -5379,48 +6700,48 @@ jest-github-actions-reporter@1.0.3: dependencies: "@actions/core" "^1.2.0" -jest-haste-map@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.3.1.tgz#7656fbd64bf48bda904e759fc9d93e2c807353ee" - integrity sha512-lYfNZIzwPccDJZIyk9Iz5iQMM/MH56NIIcGj7AFU1YyA4ewWFBl8z+YPJuSCRML/ee2cCt2y3W4K3VXPT6Nhzg== +jest-haste-map@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.4.2.tgz#7fc7d5e568cca704284f4850885b74a0b8b87587" + integrity sha512-foiyAEePORUN2eeJnOtcM1y8qW0ShEd9kTjWVL4sVaMcuCJM6gtHegvYPBRT0mpI/bs4ueThM90+Eoj2ncoNsA== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" "@types/graceful-fs" "^4.1.2" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.4" - jest-regex-util "^27.0.6" - jest-serializer "^27.0.6" - jest-util "^27.3.1" - jest-worker "^27.3.1" + jest-regex-util "^27.4.0" + jest-serializer "^27.4.0" + jest-util "^27.4.2" + jest-worker "^27.4.2" micromatch "^4.0.4" walker "^1.0.7" optionalDependencies: fsevents "^2.3.2" -jest-jasmine2@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.3.1.tgz#df6d3d07c7dafc344feb43a0072a6f09458d32b0" - integrity sha512-WK11ZUetDQaC09w4/j7o4FZDUIp+4iYWH/Lik34Pv7ukL+DuXFGdnmmi7dT58J2ZYKFB5r13GyE0z3NPeyJmsg== +jest-jasmine2@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.4.2.tgz#c956c88b9c05ca22afdc779deebc2890cb891797" + integrity sha512-VO/fyAJSH9u0THjbteFiL8qc93ufU+yW+bdieDc8tzTCWwlWzO53UHS5nFK1qmE8izb5Smkn+XHlVt6/l06MKQ== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^27.3.1" - "@jest/source-map" "^27.0.6" - "@jest/test-result" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/environment" "^27.4.2" + "@jest/source-map" "^27.4.0" + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^27.3.1" + expect "^27.4.2" is-generator-fn "^2.0.0" - jest-each "^27.3.1" - jest-matcher-utils "^27.3.1" - jest-message-util "^27.3.1" - jest-runtime "^27.3.1" - jest-snapshot "^27.3.1" - jest-util "^27.3.1" - pretty-format "^27.3.1" + jest-each "^27.4.2" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-runtime "^27.4.2" + jest-snapshot "^27.4.2" + jest-util "^27.4.2" + pretty-format "^27.4.2" throat "^6.0.1" jest-junit@13.0.0: @@ -5433,13 +6754,13 @@ jest-junit@13.0.0: uuid "^8.3.2" xml "^1.0.1" -jest-leak-detector@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.3.1.tgz#7fb632c2992ef707a1e73286e1e704f9cc1772b2" - integrity sha512-78QstU9tXbaHzwlRlKmTpjP9k4Pvre5l0r8Spo4SbFFVy/4Abg9I6ZjHwjg2QyKEAMg020XcjP+UgLZIY50yEg== +jest-leak-detector@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.4.2.tgz#7fc3120893a7a911c553f3f2bdff9faa4454abbb" + integrity sha512-ml0KvFYZllzPBJWDei3mDzUhyp/M4ubKebX++fPaudpe8OsxUE+m+P6ciVLboQsrzOCWDjE20/eXew9QMx/VGw== dependencies: - jest-get-type "^27.3.1" - pretty-format "^27.3.1" + jest-get-type "^27.4.0" + pretty-format "^27.4.2" jest-matcher-utils@27.0.6: version "27.0.6" @@ -5461,15 +6782,15 @@ jest-matcher-utils@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-matcher-utils@^27.0.6, jest-matcher-utils@^27.2.4, jest-matcher-utils@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz#257ad61e54a6d4044e080d85dbdc4a08811e9c1c" - integrity sha512-hX8N7zXS4k+8bC1Aj0OWpGb7D3gIXxYvPNK1inP5xvE4ztbz3rc4AkI6jGVaerepBnfWB17FL5lWFJT3s7qo8w== +jest-matcher-utils@^27.0.6, jest-matcher-utils@^27.2.4, jest-matcher-utils@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.4.2.tgz#d17c5038607978a255e0a9a5c32c24e984b6c60b" + integrity sha512-jyP28er3RRtMv+fmYC/PKG8wvAmfGcSNproVTW2Y0P/OY7/hWUOmsPfxN1jOhM+0u2xU984u2yEagGivz9OBGQ== dependencies: chalk "^4.0.0" - jest-diff "^27.3.1" - jest-get-type "^27.3.1" - pretty-format "^27.3.1" + jest-diff "^27.4.2" + jest-get-type "^27.4.0" + pretty-format "^27.4.2" jest-message-util@^26.6.2: version "26.6.2" @@ -5486,18 +6807,18 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" -jest-message-util@^27.0.6, jest-message-util@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.3.1.tgz#f7c25688ad3410ab10bcb862bcfe3152345c6436" - integrity sha512-bh3JEmxsTZ/9rTm0jQrPElbY2+y48Rw2t47uMfByNyUVR+OfPh4anuyKsGqsNkXk/TI4JbLRZx+7p7Hdt6q1yg== +jest-message-util@^27.0.6, jest-message-util@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.4.2.tgz#07f3f1bf207d69cf798ce830cc57f1a849f99388" + integrity sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.4" micromatch "^4.0.4" - pretty-format "^27.3.1" + pretty-format "^27.4.2" slash "^3.0.0" stack-utils "^2.0.3" @@ -5508,12 +6829,12 @@ jest-mock-extended@2.0.4: dependencies: ts-essentials "^7.0.3" -jest-mock@^27.3.0: - version "27.3.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.3.0.tgz#ddf0ec3cc3e68c8ccd489bef4d1f525571a1b867" - integrity sha512-ziZiLk0elZOQjD08bLkegBzv5hCABu/c8Ytx45nJKkysQwGaonvmTxwjLqEA4qGdasq9o2I8/HtdGMNnVsMTGw== +jest-mock@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.4.2.tgz#184ff197a25491bfe4570c286daa5d62eb760b88" + integrity sha512-PDDPuyhoukk20JrQKeofK12hqtSka7mWH0QQuxSNgrdiPsrnYYLS6wbzu/HDlxZRzji5ylLRULeuI/vmZZDrYA== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -5526,76 +6847,76 @@ jest-regex-util@^26.0.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-regex-util@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" - integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== +jest-regex-util@^27.0.6, jest-regex-util@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.4.0.tgz#e4c45b52653128843d07ad94aec34393ea14fbca" + integrity sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg== -jest-resolve-dependencies@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.3.1.tgz#85b99bdbdfa46e2c81c6228fc4c91076f624f6e2" - integrity sha512-X7iLzY8pCiYOnvYo2YrK3P9oSE8/3N2f4pUZMJ8IUcZnT81vlSonya1KTO9ZfKGuC+svE6FHK/XOb8SsoRUV1A== +jest-resolve-dependencies@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.2.tgz#2f4f363cca26f75a22aefd496f9c7ae65b3de37f" + integrity sha512-hb++cTpqvOWfU49MCP/JQkxmnrhKoAVqXWFjgYXswRSVGk8Q6bDTSvhbCeYXDtXaymY0y7WrrSIlKogClcKJuw== dependencies: - "@jest/types" "^27.2.5" - jest-regex-util "^27.0.6" - jest-snapshot "^27.3.1" + "@jest/types" "^27.4.2" + jest-regex-util "^27.4.0" + jest-snapshot "^27.4.2" -jest-resolve@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.3.1.tgz#0e5542172a1aa0270be6f66a65888647bdd74a3e" - integrity sha512-Dfzt25CFSPo3Y3GCbxynRBZzxq9AdyNN+x/v2IqYx6KVT5Z6me2Z/PsSGFSv3cOSUZqJ9pHxilao/I/m9FouLw== +jest-resolve@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.4.2.tgz#d3e4cbee7acb4a4f8c8bfc270767bec34d2aefaf" + integrity sha512-d/zqPjxCzMqHlOdRTg8cTpO9jY+1/T74KazT8Ws/LwmwxV5sRMWOkiLjmzUCDj/5IqA5XHNK4Hkmlq9Kdpb9Sg== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" chalk "^4.0.0" graceful-fs "^4.2.4" - jest-haste-map "^27.3.1" + jest-haste-map "^27.4.2" jest-pnp-resolver "^1.2.2" - jest-util "^27.3.1" - jest-validate "^27.3.1" + jest-util "^27.4.2" + jest-validate "^27.4.2" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.3.1.tgz#1d594dcbf3bd8600a7e839e790384559eaf96e3e" - integrity sha512-r4W6kBn6sPr3TBwQNmqE94mPlYVn7fLBseeJfo4E2uCTmAyDFm2O5DYAQAFP7Q3YfiA/bMwg8TVsciP7k0xOww== +jest-runner@^27.4.3: + version "27.4.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.4.3.tgz#9f05d4733829787778e8a143ade913834d0828dc" + integrity sha512-JgR6Om/j22Fd6ZUUIGTWNcCtuZVYbNrecb4k89W4UyFJoRtHpo2zMKWkmFFFJoqwWGrfrcPLnVBIgkJiTV3cyA== dependencies: - "@jest/console" "^27.3.1" - "@jest/environment" "^27.3.1" - "@jest/test-result" "^27.3.1" - "@jest/transform" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/console" "^27.4.2" + "@jest/environment" "^27.4.2" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" chalk "^4.0.0" emittery "^0.8.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-docblock "^27.0.6" - jest-environment-jsdom "^27.3.1" - jest-environment-node "^27.3.1" - jest-haste-map "^27.3.1" - jest-leak-detector "^27.3.1" - jest-message-util "^27.3.1" - jest-resolve "^27.3.1" - jest-runtime "^27.3.1" - jest-util "^27.3.1" - jest-worker "^27.3.1" + jest-docblock "^27.4.0" + jest-environment-jsdom "^27.4.3" + jest-environment-node "^27.4.2" + jest-haste-map "^27.4.2" + jest-leak-detector "^27.4.2" + jest-message-util "^27.4.2" + jest-resolve "^27.4.2" + jest-runtime "^27.4.2" + jest-util "^27.4.2" + jest-worker "^27.4.2" source-map-support "^0.5.6" throat "^6.0.1" -jest-runtime@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.3.1.tgz#80fa32eb85fe5af575865ddf379874777ee993d7" - integrity sha512-qtO6VxPbS8umqhEDpjA4pqTkKQ1Hy4ZSi9mDVeE9Za7LKBo2LdW2jmT+Iod3XFaJqINikZQsn2wEi0j9wPRbLg== - dependencies: - "@jest/console" "^27.3.1" - "@jest/environment" "^27.3.1" - "@jest/globals" "^27.3.1" - "@jest/source-map" "^27.0.6" - "@jest/test-result" "^27.3.1" - "@jest/transform" "^27.3.1" - "@jest/types" "^27.2.5" +jest-runtime@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.4.2.tgz#d72da8a0e97366c16ad515a2c437191a72600d38" + integrity sha512-eqPgcBaUNaw6j8T5M+dnfAEh6MIrh2YmtskCr9sl50QYpD22Sg+QqHw3J3nmaLzVMbBtOMHFFxLF0Qx8MsZVFQ== + dependencies: + "@jest/console" "^27.4.2" + "@jest/environment" "^27.4.2" + "@jest/globals" "^27.4.2" + "@jest/source-map" "^27.4.0" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.2" + "@jest/types" "^27.4.2" "@types/yargs" "^16.0.0" chalk "^4.0.0" cjs-module-lexer "^1.0.0" @@ -5604,22 +6925,22 @@ jest-runtime@^27.3.1: exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.4" - jest-haste-map "^27.3.1" - jest-message-util "^27.3.1" - jest-mock "^27.3.0" - jest-regex-util "^27.0.6" - jest-resolve "^27.3.1" - jest-snapshot "^27.3.1" - jest-util "^27.3.1" - jest-validate "^27.3.1" + jest-haste-map "^27.4.2" + jest-message-util "^27.4.2" + jest-mock "^27.4.2" + jest-regex-util "^27.4.0" + jest-resolve "^27.4.2" + jest-snapshot "^27.4.2" + jest-util "^27.4.2" + jest-validate "^27.4.2" slash "^3.0.0" strip-bom "^4.0.0" yargs "^16.2.0" -jest-serializer@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.0.6.tgz#93a6c74e0132b81a2d54623251c46c498bb5bec1" - integrity sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA== +jest-serializer@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.4.0.tgz#34866586e1cae2388b7d12ffa2c7819edef5958a" + integrity sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ== dependencies: "@types/node" "*" graceful-fs "^4.2.4" @@ -5632,10 +6953,10 @@ jest-silent-reporter@0.5.0: chalk "^4.0.0" jest-util "^26.0.0" -jest-snapshot@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.3.1.tgz#1da5c0712a252d70917d46c037054f5918c49ee4" - integrity sha512-APZyBvSgQgOT0XumwfFu7X3G5elj6TGhCBLbBdn3R1IzYustPGPE38F51dBWMQ8hRXa9je0vAdeVDtqHLvB6lg== +jest-snapshot@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.4.2.tgz#bd1ea04a8fab402e5ab18b788809fa597ddff532" + integrity sha512-DI7lJlNIu6WSQ+esqhnJzEzU70+dV+cNjoF1c+j5FagWEd3KtOyZvVliAH0RWNQ6KSnAAnKSU0qxJ8UXOOhuUQ== dependencies: "@babel/core" "^7.7.2" "@babel/generator" "^7.7.2" @@ -5643,23 +6964,23 @@ jest-snapshot@^27.3.1: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.0.0" - "@jest/transform" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/transform" "^27.4.2" + "@jest/types" "^27.4.2" "@types/babel__traverse" "^7.0.4" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^27.3.1" + expect "^27.4.2" graceful-fs "^4.2.4" - jest-diff "^27.3.1" - jest-get-type "^27.3.1" - jest-haste-map "^27.3.1" - jest-matcher-utils "^27.3.1" - jest-message-util "^27.3.1" - jest-resolve "^27.3.1" - jest-util "^27.3.1" + jest-diff "^27.4.2" + jest-get-type "^27.4.0" + jest-haste-map "^27.4.2" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-resolve "^27.4.2" + jest-util "^27.4.2" natural-compare "^1.4.0" - pretty-format "^27.3.1" + pretty-format "^27.4.2" semver "^7.3.2" jest-util@^26.0.0: @@ -5674,67 +6995,67 @@ jest-util@^26.0.0: is-ci "^2.0.0" micromatch "^4.0.2" -jest-util@^27.0.0, jest-util@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.3.1.tgz#a58cdc7b6c8a560caac9ed6bdfc4e4ff23f80429" - integrity sha512-8fg+ifEH3GDryLQf/eKZck1DEs2YuVPBCMOaHQxVVLmQwl/CDhWzrvChTX4efLZxGrw+AA0mSXv78cyytBt/uw== +jest-util@^27.0.0, jest-util@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.4.2.tgz#ed95b05b1adfd761e2cda47e0144c6a58e05a621" + integrity sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.4" picomatch "^2.2.3" -jest-validate@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.3.1.tgz#3a395d61a19cd13ae9054af8cdaf299116ef8a24" - integrity sha512-3H0XCHDFLA9uDII67Bwi1Vy7AqwA5HqEEjyy934lgVhtJ3eisw6ShOF1MDmRPspyikef5MyExvIm0/TuLzZ86Q== +jest-validate@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.4.2.tgz#eecfcc1b1c9429aa007da08a2bae4e32a81bbbc3" + integrity sha512-hWYsSUej+Fs8ZhOm5vhWzwSLmVaPAxRy+Mr+z5MzeaHm9AxUpXdoVMEW4R86y5gOobVfBsMFLk4Rb+QkiEpx1A== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^27.3.1" + jest-get-type "^27.4.0" leven "^3.1.0" - pretty-format "^27.3.1" + pretty-format "^27.4.2" -jest-watcher@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.3.1.tgz#ba5e0bc6aa843612b54ddb7f009d1cbff7e05f3e" - integrity sha512-9/xbV6chABsGHWh9yPaAGYVVKurWoP3ZMCv6h+O1v9/+pkOroigs6WzZ0e9gLP/njokUwM7yQhr01LKJVMkaZA== +jest-watcher@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.4.2.tgz#c9037edfd80354c9fe90de4b6f8b6e2b8e736744" + integrity sha512-NJvMVyyBeXfDezhWzUOCOYZrUmkSCiatpjpm+nFUid74OZEHk6aMLrZAukIiFDwdbqp6mTM6Ui1w4oc+8EobQg== dependencies: - "@jest/test-result" "^27.3.1" - "@jest/types" "^27.2.5" + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^27.3.1" + jest-util "^27.4.2" string-length "^4.0.1" -jest-worker@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.3.1.tgz#0def7feae5b8042be38479799aeb7b5facac24b2" - integrity sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g== +jest-worker@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.2.tgz#0fb123d50955af1a450267787f340a1bf7e12bc4" + integrity sha512-0QMy/zPovLfUPyHuOuuU4E+kGACXXE84nRnq6lBVI9GJg5DCBiA97SATi+ZP8CpiJwEQy1oCPjRBf8AnLjN+Ag== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-27.3.1.tgz#b5bab64e8f56b6f7e275ba1836898b0d9f1e5c8a" - integrity sha512-U2AX0AgQGd5EzMsiZpYt8HyZ+nSVIh5ujQ9CPp9EQZJMjXIiSZpJNweZl0swatKRoqHWgGKM3zaSwm4Zaz87ng== +jest@27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.4.2.tgz#4fb1211ad0b9955ef09a11b96684180a90638985" + integrity sha512-TAReynFYCfHNcrL+8Z74WPGafLFLF++bGkrpcsk6cO5G9S2VuJGhu2c44YFForMgF0GlYmtbpmeznkvZpNgTxg== dependencies: - "@jest/core" "^27.3.1" + "@jest/core" "^27.4.2" import-local "^3.0.2" - jest-cli "^27.3.1" + jest-cli "^27.4.2" js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -5830,15 +7151,10 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0, json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -5889,13 +7205,13 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" just-diff-apply@^3.0.0: @@ -5908,6 +7224,11 @@ just-diff@^3.0.1: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-3.1.1.tgz#d50c597c6fd4776495308c63bdee1b6839082647" integrity sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ== +just-extend@^4.0.2: + version "4.2.1" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" + integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== + keyv@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.4.tgz#f040b236ea2b06ed15ed86fbef8407e1a1c8e376" @@ -5915,7 +7236,7 @@ keyv@^4.0.0: dependencies: json-buffer "3.0.1" -kind-of@>=6.0.3, kind-of@^6.0.3: +kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -5925,6 +7246,11 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +klona@2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -6057,9 +7383,9 @@ libnpmversion@*: stringify-package "^1.0.1" lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== linkify-it@^3.0.1: version "3.0.3" @@ -6105,16 +7431,16 @@ lodash.capitalize@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" @@ -6145,11 +7471,6 @@ lodash.set@^4.3.2: resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= - lodash.uniqby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" @@ -6185,10 +7506,10 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -luxon@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.0.2.tgz#11f2cd4a11655fdf92e076b5782d7ede5bcdd133" - integrity sha512-ZRioYLCgRHrtTORaZX1mx+jtxKtKuI5ZDvHNAmqpUzGqSrR+tL4FVLn/CUGMA3h0+AKD1MAxGI5GnCqR5txNqg== +luxon@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.1.1.tgz#34052f7a33a7989767637be7cf80b47db264ff88" + integrity sha512-6VQVNw7+kQu3hL1ZH5GyOhnk8uZm21xS7XJ/6vDZaFNcb62dpFDKcH8TI5NkoZOdMRxr7af7aYGrJlE/Wv0i1w== make-dir@^3.0.0: version "3.1.0" @@ -6432,22 +7753,22 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@~4.0.4: braces "^3.0.1" picomatch "^2.2.3" -mime-db@1.50.0: - version "1.50.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" - integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.33" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" - integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== + version "2.1.34" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== dependencies: - mime-db "1.50.0" + mime-db "1.51.0" -mime@^2.4.3: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" - integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== mimic-fn@^2.1.0: version "2.1.0" @@ -6578,10 +7899,10 @@ mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mock-fs@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.1.1.tgz#d4c95e916abf400664197079d7e399d133bb6048" - integrity sha512-p/8oZ3qvfKGPw+4wdVCyjDxa6wn2tP0TCf3WXC1UyUBAevezPn1TtOoxtMYVbZu/S/iExg+Ghed1busItj2CEw== +mock-fs@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.1.2.tgz#6fa486e06d00f8793a8d2228de980eff93ce6db7" + integrity sha512-YkjQkdLulFrz0vD4BfNQdQRVmgycXTV7ykuHMlyv+C8WCHazpkiQRDthwa02kSyo8wKnY9wRptHfQLgmf0eR+A== mockdate@3.0.5: version "3.0.5" @@ -6653,10 +7974,10 @@ nan@^2.14.0, nan@^2.14.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -nanoid@^2.1.0: - version "2.1.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" - integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== +nanoid@3.1.30: + version "3.1.30" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" + integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== natural-compare@^1.4.0: version "1.4.0" @@ -6688,10 +8009,21 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nock@13.1.4: - version "13.1.4" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.1.4.tgz#367c917d4c532a889404b85ade92762c29e80262" - integrity sha512-hr5+mknLpIbTOXifB13lx9mAKF1zQPUCMh53Galx79ic5opvNOd55jiB0iGCp2xqh+hwnFbNE/ddBKHsJNQrbw== +nise@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.0.tgz#713ef3ed138252daef20ec035ab62b7a28be645c" + integrity sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^7.0.4" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + +nock@13.2.1: + version "13.2.1" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.1.tgz#fcf5bdb9bb9f0554a84c25d3333166c0ffd80858" + integrity sha512-CoHAabbqq/xZEknubuyQMjq6Lfi5b7RtK6SoNK6m40lebGp3yiMagWtIoYaw2s9sISD7wPuCfwFpivVHX/35RA== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -6713,16 +8045,16 @@ node-fetch@^2.6.1: whatwg-url "^5.0.0" node-gyp@*, node-gyp@^8.0.0, node-gyp@^8.2.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.3.0.tgz#ebc36a146d45095e1c6af6ccb0e47d1c8fc3fe69" - integrity sha512-e+vmKyTiybKgrmvs4M2REFKCnOd+NcrAAnn99Yko6NQA+zZdMlRvbIUHojfsHrSQ1CddLgZnHicnEVgDHziJzA== + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== dependencies: env-paths "^2.2.0" glob "^7.1.4" graceful-fs "^4.2.6" make-fetch-happen "^9.1.0" nopt "^5.0.0" - npmlog "^4.1.2" + npmlog "^6.0.0" rimraf "^3.0.2" semver "^7.3.5" tar "^6.1.2" @@ -6884,7 +8216,19 @@ npm-profile@*: dependencies: npm-registry-fetch "^11.0.0" -npm-registry-fetch@*, npm-registry-fetch@^11.0.0: +npm-registry-fetch@*: + version "12.0.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-12.0.0.tgz#53d8c94f7c37293707b23728864710b76d3a3ca5" + integrity sha512-nd1I90UHoETjgWpo3GbcoM1l2S4JCUpzDcahU4x/GVCiDQ6yRiw2KyDoPVD8+MqODbPtWwHHGiyc4O5sgdEqPQ== + dependencies: + make-fetch-happen "^9.0.1" + minipass "^3.1.3" + minipass-fetch "^1.3.0" + minipass-json-stream "^1.0.1" + minizlib "^2.0.0" + npm-package-arg "^8.0.0" + +npm-registry-fetch@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== @@ -6928,85 +8272,85 @@ npm@^7.0.0: resolved "https://registry.yarnpkg.com/npm/-/npm-7.24.2.tgz#861117af8241bea592289f22407230e5300e59ca" integrity sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ== dependencies: - "@isaacs/string-locale-compare" "^1.1.0" - "@npmcli/arborist" "^2.9.0" - "@npmcli/ci-detect" "^1.2.0" - "@npmcli/config" "^2.3.0" - "@npmcli/map-workspaces" "^1.0.4" - "@npmcli/package-json" "^1.0.1" - "@npmcli/run-script" "^1.8.6" - abbrev "~1.1.1" - ansicolors "~0.3.2" - ansistyles "~0.1.3" - archy "~1.0.0" - cacache "^15.3.0" - chalk "^4.1.2" - chownr "^2.0.0" - cli-columns "^3.1.2" - cli-table3 "^0.6.0" - columnify "~1.5.4" - fastest-levenshtein "^1.0.12" - glob "^7.2.0" - graceful-fs "^4.2.8" - hosted-git-info "^4.0.2" - ini "^2.0.0" - init-package-json "^2.0.5" - is-cidr "^4.0.2" - json-parse-even-better-errors "^2.3.1" - libnpmaccess "^4.0.2" - libnpmdiff "^2.0.4" - libnpmexec "^2.0.1" - libnpmfund "^1.1.0" - libnpmhook "^6.0.2" - libnpmorg "^2.0.2" - libnpmpack "^2.0.1" - libnpmpublish "^4.0.1" - libnpmsearch "^3.1.1" - libnpmteam "^2.0.3" - libnpmversion "^1.2.1" - make-fetch-happen "^9.1.0" - minipass "^3.1.3" - minipass-pipeline "^1.2.4" - mkdirp "^1.0.4" - mkdirp-infer-owner "^2.0.0" - ms "^2.1.2" - node-gyp "^7.1.2" - nopt "^5.0.0" - npm-audit-report "^2.1.5" - npm-install-checks "^4.0.0" - npm-package-arg "^8.1.5" - npm-pick-manifest "^6.1.1" - npm-profile "^5.0.3" - npm-registry-fetch "^11.0.0" - npm-user-validate "^1.0.1" - npmlog "^5.0.1" - opener "^1.5.2" - pacote "^11.3.5" - parse-conflict-json "^1.1.1" - qrcode-terminal "^0.12.0" - read "~1.0.7" - read-package-json "^4.1.1" - read-package-json-fast "^2.0.3" - readdir-scoped-modules "^1.1.0" - rimraf "^3.0.2" - semver "^7.3.5" - ssri "^8.0.1" - tar "^6.1.11" - text-table "~0.2.0" - tiny-relative-date "^1.3.0" - treeverse "^1.0.4" - validate-npm-package-name "~3.0.0" - which "^2.0.2" - write-file-atomic "^3.0.3" - -npmlog@*: - version "5.0.1" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" - integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + "@isaacs/string-locale-compare" "*" + "@npmcli/arborist" "*" + "@npmcli/ci-detect" "*" + "@npmcli/config" "*" + "@npmcli/map-workspaces" "*" + "@npmcli/package-json" "*" + "@npmcli/run-script" "*" + abbrev "*" + ansicolors "*" + ansistyles "*" + archy "*" + cacache "*" + chalk "*" + chownr "*" + cli-columns "*" + cli-table3 "*" + columnify "*" + fastest-levenshtein "*" + glob "*" + graceful-fs "*" + hosted-git-info "*" + ini "*" + init-package-json "*" + is-cidr "*" + json-parse-even-better-errors "*" + libnpmaccess "*" + libnpmdiff "*" + libnpmexec "*" + libnpmfund "*" + libnpmhook "*" + libnpmorg "*" + libnpmpack "*" + libnpmpublish "*" + libnpmsearch "*" + libnpmteam "*" + libnpmversion "*" + make-fetch-happen "*" + minipass "*" + minipass-pipeline "*" + mkdirp "*" + mkdirp-infer-owner "*" + ms "*" + node-gyp "*" + nopt "*" + npm-audit-report "*" + npm-install-checks "*" + npm-package-arg "*" + npm-pick-manifest "*" + npm-profile "*" + npm-registry-fetch "*" + npm-user-validate "*" + npmlog "*" + opener "*" + pacote "*" + parse-conflict-json "*" + qrcode-terminal "*" + read "*" + read-package-json "*" + read-package-json-fast "*" + readdir-scoped-modules "*" + rimraf "*" + semver "*" + ssri "*" + tar "*" + text-table "*" + tiny-relative-date "*" + treeverse "*" + validate-npm-package-name "*" + which "*" + write-file-atomic "*" + +npmlog@*, npmlog@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.0.tgz#ba9ef39413c3d936ea91553db7be49c34ad0520c" + integrity sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q== dependencies: are-we-there-yet "^2.0.0" console-control-strings "^1.1.0" - gauge "^3.0.0" + gauge "^4.0.0" set-blocking "^2.0.0" npmlog@^4.1.2: @@ -7041,7 +8385,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -7066,15 +8410,6 @@ object.assign@^4.1.0, object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.entries@^1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" - integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -7110,10 +8445,10 @@ opener@*: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -openpgp@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.0.0.tgz#2da0ee406c8834223ae928a9a214f4811f83f923" - integrity sha512-H4Jsj9Bp1KFQ/w520M1d2x45iz9V39Lf+IwIXmUaBmJAMagAt0zanqmWeFzIMJUYmrHTcm6fO/rpc6aftFUHbA== +openpgp@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.0.1.tgz#fe355dcdc2074e505d5c22ee51e880a8296bd0ab" + integrity sha512-J9HGIcXumwczJwX3JvgshWYtkhsOJHm5ZPd1ipJ1BqrZL06NgqV/EfJyF3ThOlNV2rY0MGWdS8L8/kKyeo3sXg== dependencies: asn1.js "^5.0.0" @@ -7267,7 +8602,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pacote@*, pacote@^12.0.0: +pacote@*, pacote@^12.0.0, pacote@^12.0.2: version "12.0.2" resolved "https://registry.yarnpkg.com/pacote/-/pacote-12.0.2.tgz#14ae30a81fe62ec4fc18c071150e6763e932527c" integrity sha512-Ar3mhjcxhMzk+OVZ8pbnXdb0l8+pimvlsqBGRNkble2NVgyqOGE3yrCGi/lAYq7E7NRDMz89R1Wx5HIMCGgeYg== @@ -7430,6 +8765,13 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -7516,10 +8858,10 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prettier@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" - integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== +prettier@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.0.tgz#a6370e2d4594e093270419d9cc47f7670488f893" + integrity sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg== pretty-bytes@^5.1.0: version "5.6.0" @@ -7536,20 +8878,20 @@ pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^27.0.0, pretty-format@^27.0.6, pretty-format@^27.3.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.3.1.tgz#7e9486365ccdd4a502061fa761d3ab9ca1b78df5" - integrity sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA== +pretty-format@^27.0.0, pretty-format@^27.0.6, pretty-format@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.2.tgz#e4ce92ad66c3888423d332b40477c87d1dac1fb8" + integrity sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw== dependencies: - "@jest/types" "^27.2.5" + "@jest/types" "^27.4.2" ansi-regex "^5.0.1" ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-quick@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-3.1.1.tgz#93ca4e2dd38cc4e970e3f54a0ead317a25454688" - integrity sha512-ZYLGiMoV2jcaas3vTJrLvKAYsxDoXQBUn8OSTxkl67Fyov9lyXivJTl0+2WVh+y6EovGcw7Lm5ThYpH+Sh3XxQ== +pretty-quick@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-3.1.2.tgz#89d8741af7122cbd7f34182df746c5a7ea360b5c" + integrity sha512-T+fpTJrDjTzewql4p3lKrRA7z3MrNyjBK1MKeaBm5PpKwATgVm885TpY7TgY8KFt5Q1Qn3QDseRQcyX9AKTKkA== dependencies: chalk "^3.0.0" execa "^4.0.0" @@ -7881,7 +9223,7 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regexpp@^3.1.0: +regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== @@ -7961,11 +9303,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - resolve-alpn@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -8088,12 +9425,12 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -semantic-release@18.0.0: - version "18.0.0" - resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-18.0.0.tgz#b44b7101ed0525c041b984f74854852be67341cc" - integrity sha512-/Szyhq5DTZCYry/aZqpBbK/kqv10ydn6oiiaYOXtPgDbAIkqidZcQOm+mfYFJ0sBTUaOYCKMlcPMgJycP7jDYQ== +semantic-release@18.0.1: + version "18.0.1" + resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-18.0.1.tgz#df5ad44b9c2fd67fe3cdbc660b3d1f55298b9f34" + integrity sha512-xTdKCaEnCzHr+Fqyhg/5I8P9pvY9z7WHa8TFCYIwcdPbuzAtQShOTzw3VNPsqBT+Yq1kFyBQFBKBYkGOlqWmfA== dependencies: - "@semantic-release/commit-analyzer" "^9.0.0" + "@semantic-release/commit-analyzer" "^9.0.2" "@semantic-release/error" "^3.0.0" "@semantic-release/github" "^8.0.0" "@semantic-release/npm" "^8.0.0" @@ -8223,13 +9560,6 @@ shlex@2.1.0: resolved "https://registry.yarnpkg.com/shlex/-/shlex-2.1.0.tgz#4f8fbf75c4a9956283e4095fa5eac3f4969f6a6b" integrity sha512-Tk8PjohJbWpGu2NtAlsEi/9AS4GU2zW2ZWLFrWRDskZpSJmyBIU3nTkBtocxD90r3w4BwRevsNtIqIP9HMuYiQ== -shortid@2.2.16: - version "2.2.16" - resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608" - integrity sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g== - dependencies: - nanoid "^2.1.0" - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -8245,9 +9575,9 @@ sigmund@^1.0.1: integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.5" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" - integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== + version "3.0.6" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" + integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== signale@^1.2.1: version "1.4.0" @@ -8258,15 +9588,27 @@ signale@^1.2.1: figures "^2.0.0" pkg-conf "^2.1.0" -simple-git@2.47.0: - version "2.47.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.47.0.tgz#9693b471de7911901f703b4c403fc3e8774cd9be" - integrity sha512-+HfCpqPBEZTPWiW9fPdbiPJDslM22MLqrktfzNKyI2pWaJa6DhfNVx4Mds04KZzVv5vjC9/ksw3y5gVf8ECWDg== +simple-git@2.47.1: + version "2.47.1" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.47.1.tgz#59a5c1af598b30b6234b98c9e22f8c01e5b7e06c" + integrity sha512-DF4rnBr4uzMQsreqxHg8t1wN4Pi3kj/shBVT1OO+aBkBnscCZ02tynKHc9cx3StNPnItHWAaoN31qkRNDhh5Ow== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" debug "^4.3.2" +sinon@^11.1.1: + version "11.1.2" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-11.1.2.tgz#9e78850c747241d5c59d1614d8f9cbe8840e8674" + integrity sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw== + dependencies: + "@sinonjs/commons" "^1.8.3" + "@sinonjs/fake-timers" "^7.1.2" + "@sinonjs/samsam" "^6.0.2" + diff "^5.0.0" + nise "^5.1.0" + supports-color "^7.2.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -8277,19 +9619,10 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -slugify@1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.1.tgz#a5fcaef29f4e57c6e932ce7044b6ffd9cf81b641" - integrity sha512-5ofqMTbetNhxlzjYYLBaZFQd6oiTuSkQlyfPEFIMwgUABlZQ0hbk5xIV9Ydd5jghWeRoO7GkiJliUvTpLOjNRA== +slugify@1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.3.tgz#325aec50871acfb17976f2d3cb09ee1e7ab563be" + integrity sha512-1MPyqnIhgiq+/0iDJyqSJHENdnH5MMIlgJIBxmkRMzTNKlS/QsN5dXsB+MdDq4E6w0g9jFA4XOTRkVDjDae/2w== smart-buffer@^4.1.0: version "4.2.0" @@ -8297,9 +9630,9 @@ smart-buffer@^4.1.0: integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== socks-proxy-agent@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3" - integrity sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg== + version "6.1.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" + integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== dependencies: agent-base "^6.0.2" debug "^4.3.1" @@ -8314,9 +9647,9 @@ socks@^2.6.1: smart-buffer "^4.1.0" source-map-support@^0.5.6: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -8363,9 +9696,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.10" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" - integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== + version "3.0.11" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== split-on-first@^1.0.0: version "1.1.0" @@ -8483,14 +9816,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.1 || ^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -8558,13 +9883,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -"strip-ansi@^3.0.1 || ^4.0.0", strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -8604,7 +9922,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -8631,18 +9949,6 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^6.0.9: - version "6.7.2" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.2.tgz#a8d39b9f5966693ca8b0feba270a78722cbaf3b0" - integrity sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g== - dependencies: - ajv "^8.0.1" - lodash.clonedeep "^4.5.0" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - tar-stream@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" @@ -8869,9 +10175,9 @@ ts-node@10.4.0: yn "3.1.1" tsconfig-paths@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" - integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== + version "3.12.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" @@ -8883,16 +10189,16 @@ tslib@2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== +tslib@2.3.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tslib@^1.11.1, tslib@^1.13.0, tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -8931,15 +10237,15 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@4.0.8: +type-detect@4.0.8, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.5.2.tgz#d6a5247b8019716b300d9023fa7b1b02016dd864" - integrity sha512-WMbytmAs5PUTqwGJRE+WoRrD2S0bYFtHX8k4Y/1l18CG5kqA3keJud9pPQ/r30FE9n8XRFCXF9BbccHIZzRYJw== +type-fest@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.7.0.tgz#6d58aa78d48f0968110a4ab1f75f563ae6cde572" + integrity sha512-gmfzrsfDuoQUpqteXPE1X8D2GdtEAhP+X9pdXj3xBbg86OO8ZtSTtJ9BVopDyWtdNuwbOXAYdluX4o/O65qebA== type-fest@^0.13.1: version "0.13.1" @@ -8992,10 +10298,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.4.4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== +typescript@4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" + integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" @@ -9003,9 +10309,9 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== uglify-js@^3.1.4: - version "3.14.2" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.2.tgz#d7dd6a46ca57214f54a2d0a43cad0f35db82ac99" - integrity sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A== + version "3.14.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.4.tgz#68756f17d1b90b9d289341736cb9a567d6882f90" + integrity sha512-AbiSR44J0GoCeV81+oxcy/jDOElO2Bx3d0MfQCUShq7JRXaM4KtQopZsq2vFv8bCq2yMaGrw1FgygUd03RyRDA== unbox-primitive@^1.0.1: version "1.0.1" @@ -9331,9 +10637,9 @@ write-file-atomic@*, write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: typedarray-to-buffer "^3.1.5" ws@^7.4.6: - version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" - integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + version "7.5.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== xml-name-validator@^3.0.0: version "3.0.0"