diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 94f1ad45255807..a22213208c216a 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,4 +1,4 @@
-FROM containerbase/node:14.16.1@sha256:1ae9b66d0c6c36cb9993c0dd939dfb5b09553bc26798b51f3c723e0aaa7c653c
+FROM containerbase/node:14.17.0@sha256:39419f23c62d0fec2f18255d2c822d499a0d2a9cd9f6fcce4e1f05d6edce111a
# renovate: datasource=npm
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 8724fa3829eceb..211a87a8f12cc8 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,5 +1,5 @@
{
- "name": "Node.js 14",
+ "name": "Renovate",
"dockerFile": "Dockerfile",
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
diff --git a/.eslintrc.js b/.eslintrc.js
index c2c970e27d2e4b..78f298a23e44d1 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -60,6 +60,9 @@ module.exports = {
},
],
+ // disallow direct `nock` module usage as it causes memory issues.
+ 'no-restricted-imports': [2, { paths: ['nock'] }],
+
// Makes no sense to allow type inference for expression parameters, but require typing the response
'@typescript-eslint/explicit-function-return-type': [
'error',
@@ -123,6 +126,8 @@ module.exports = {
'@typescript-eslint/unbound-method': 0,
'jest/valid-title': [0, { ignoreTypeOfDescribeName: true }],
+ 'max-classes-per-file': 0,
+ 'class-methods-use-this': 0,
},
},
{
diff --git a/.github/label-actions.yml b/.github/label-actions.yml
index 284c9ad0ca306f..2e190e13eaeb97 100644
--- a/.github/label-actions.yml
+++ b/.github/label-actions.yml
@@ -28,7 +28,6 @@
The Renovate team will take a look at the reproduction repository.
- Once we confirm the provided repository reproduces the problem, the label will be changed to `reproduction:confirmed`.
'logs:problem':
comment: >
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 04df248f5066c7..29a6b83fa52bb4 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -20,8 +20,8 @@
I have verified these changes via:
- [ ] Code inspection only, or
-- [ ] Newly added unit tests, or
-- [ ] No new tests but ran on a real repository, or
+- [ ] Newly added/modified unit tests, or
+- [ ] No unit tests but ran on a real repository, or
- [ ] Both unit tests + ran on a real repository
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
index b6a22735b76e08..7a9838dc3933f5 100644
--- a/.github/workflows/build-pr.yml
+++ b/.github/workflows/build-pr.yml
@@ -8,7 +8,7 @@ env:
YARN_PACKAGE_CACHE_KEY: v1
YARN_CACHE_FOLDER: .cache/yarn
NODE_VERSION: 14
- PYTHON_VERSION: 3.8
+ PYTHON_VERSION: 3.9
SKIP_JAVA_TESTS: true
jobs:
@@ -47,7 +47,7 @@ jobs:
- name: Cache Yarn packages
id: yarn_cache_packages
- uses: actions/cache@v2.1.5
+ uses: actions/cache@v2.1.6
with:
path: ${{ env.YARN_CACHE_FOLDER }}
key: ${{ env.YARN_PACKAGE_CACHE_KEY }}-${{ runner.os }}-yarn_cache-${{ hashFiles('**/yarn.lock') }}
@@ -63,10 +63,10 @@ jobs:
run: yarn install --frozen-lockfile
- name: Unit tests
- run: yarn jest --logHeapUsage --maxWorkers=2 --ci
+ run: yarn jest --maxWorkers=2 --ci
- name: Codecov
- uses: codecov/codecov-action@v1.5.0
+ uses: codecov/codecov-action@v1.5.2
if: always()
# build after tests to exclude files
@@ -102,7 +102,7 @@ jobs:
- name: Cache Yarn packages
id: yarn_cache_packages
- uses: actions/cache@v2.1.5
+ uses: actions/cache@v2.1.6
with:
path: ${{ env.YARN_CACHE_FOLDER }}
key: ${{ env.YARN_PACKAGE_CACHE_KEY }}-${{ runner.os }}-yarn_cache-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5e674e067baaa0..8e5f4da42fbd73 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,7 +32,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [14]
- python-version: [3.8]
+ python-version: [3.9]
java-version: [11]
env:
@@ -83,7 +83,7 @@ jobs:
- name: Cache Yarn packages
id: yarn_cache_packages
- uses: actions/cache@v2.1.5
+ uses: actions/cache@v2.1.6
with:
path: ${{ env.YARN_CACHE_FOLDER }}
key: ${{ env.YARN_PACKAGE_CACHE_KEY }}-${{ runner.os }}-yarn_cache-${{ hashFiles('**/yarn.lock') }}
@@ -99,10 +99,10 @@ jobs:
run: yarn install --frozen-lockfile
- name: Unit tests
- run: yarn jest --logHeapUsage --maxWorkers=2 --ci --coverage ${{ env.coverage }}
+ run: yarn jest --maxWorkers=2 --ci --coverage ${{ env.coverage }}
- name: Codecov
- uses: codecov/codecov-action@v1.5.0
+ uses: codecov/codecov-action@v1.5.2
if: always() && env.coverage == 'true'
# build after tests to exclude build files from tests
@@ -139,7 +139,7 @@ jobs:
- name: Cache Yarn packages
id: yarn_cache_packages
- uses: actions/cache@v2.1.5
+ uses: actions/cache@v2.1.6
with:
path: ${{ env.YARN_CACHE_FOLDER }}
key: ${{ env.YARN_PACKAGE_CACHE_KEY }}-${{ runner.os }}-yarn_cache-${{ hashFiles('**/yarn.lock') }}
@@ -204,7 +204,7 @@ jobs:
fetch-depth: 0
- name: Cache Yarn packages
- uses: actions/cache@v2.1.5
+ uses: actions/cache@v2.1.6
with:
path: ${{ env.YARN_CACHE_FOLDER }}
key: ${{ env.YARN_PACKAGE_CACHE_KEY }}-${{ runner.os }}-yarn_cache-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 03e6566f709937..b63c36df975345 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -19,7 +19,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v1.0.1
# Override language selection by uncommenting this and choosing your languages
# with:
@@ -27,7 +27,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@v1
+ uses: github/codeql-action/autobuild@v1.0.1
# âšī¸ Command-line programs to run using the OS shell.
# đ https://git.io/JvXDl
@@ -41,4 +41,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v1.0.1
diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml
index 3ab6672cd840e6..cc196d285890ff 100644
--- a/.github/workflows/release-npm.yml
+++ b/.github/workflows/release-npm.yml
@@ -57,7 +57,7 @@ jobs:
- name: Cache Yarn packages
id: yarn_cache_packages
- uses: actions/cache@v2.1.5
+ uses: actions/cache@v2.1.6
with:
path: ${{ env.YARN_CACHE_FOLDER }}
key: ${{ env.YARN_PACKAGE_CACHE_KEY }}-${{ runner.os }}-yarn_cache-${{ hashFiles('**/yarn.lock') }}
diff --git a/docs/development/issue-labeling.md b/docs/development/issue-labeling.md
index a0911a26b7027d..6df84a302bef77 100644
--- a/docs/development/issue-labeling.md
+++ b/docs/development/issue-labeling.md
@@ -3,6 +3,17 @@
We try to keep issues well-classified through use of labels.
Any repository collaborator can apply labels according to the below guidelines.
+The general idea is that we have:
+
+- manager (`manager:`)
+- versioning (`versioning:`)
+- datasource (`datasource:`)
+- platform (`platform:`)
+- core functionality (`core:`)
+
+The majority of issues should have at least one of those labels.
+These labels should also map approximately to our Conventional Commit scopes.
+
## Basic knowledge about Renovate
You should know about platforms, package managers, datasources and versioning to label issues effectively.
@@ -92,6 +103,20 @@ Use [this search](https://github.com/renovatebot/renovate/issues?q=is%3Aissue+is
Use these to mark the platform that is affected by this issue.
Keep in mind that an issue can be both affecting a platform and a self hosted instance.
+### Core
+
+
+ Core labels
+
+ core:automerge
+ core:dashboard
+ core:onboarding
+ core:schedule
+
+
+
+The purpose of these labels is to allow browsing of open issues by the most commonly-used functionality, such as automerging or Dependency Dashboard.
+
### Manager
"manager" is short for "package manager".
@@ -141,7 +166,6 @@ Apply these labels when somebody opens a `feature` type issue requesting a new d
logs:problem
reproduction:needed
reproduction:provided
- reproduction:confirmed
duplicate
@@ -159,7 +183,6 @@ Add a label `logs:problem` to indicate that there's a problem with the logs, and
Add a label `reproduction:needed` if nobody's reproduced it in a public repo yet and such a reproduction is necessary before further work can be done.
Add the label `reproduction:provided` once there is a public reproduction.
-A developer will add the `reproduction:confirmed` once they have checked and confirmed the reproduction.
Add a label `duplicate` to issues/PRs that are a duplicate of an earlier issue/PR.
diff --git a/docs/development/local-development.md b/docs/development/local-development.md
index 641cc3f369ad38..2cbb96b97fa39f 100644
--- a/docs/development/local-development.md
+++ b/docs/development/local-development.md
@@ -14,7 +14,7 @@ You need the following dependencies for local development:
- Node.js `>=14.15.4`
- Yarn `^1.22.5`
- C++ compiler
-- Python `^3.8`
+- 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).
diff --git a/docs/development/new-package-manager-template.md b/docs/development/new-package-manager-template.md
index be1455617ce2ac..fb717152c2cac9 100644
--- a/docs/development/new-package-manager-template.md
+++ b/docs/development/new-package-manager-template.md
@@ -2,7 +2,7 @@
**Did you read our documentation on adding a package manager?**
-- [ ] I've read the [adding a package manager](../../docs/development/adding-a-package-manager.md) documentation.
+- [ ] I've read the [adding a package manager](/renovatebot/renovate/blob/HEAD/docs/development/adding-a-package-manager.md) documentation.
## Basics
diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index cae189ab710c46..e6b372a013fc41 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -1990,12 +1990,22 @@ In the above example, each regex manager will match a single dependency each.
If `depName` cannot be captured with a named capture group in `matchString` then it can be defined manually using this field.
It will be compiled using Handlebars and the regex `groups` result.
+### extractVersionTemplate
+
+If `extractVersion` cannot be captured with a named capture group in `matchString` then it can be defined manually using this field.
+It will be compiled using Handlebars and the regex `groups` result.
+
### lookupNameTemplate
`lookupName` is used for looking up dependency versions.
It will be compiled using Handlebars and the regex `groups` result.
It will default to the value of `depName` if left unconfigured/undefined.
+### currentValueTemplate
+
+If the `currentValue` for a dependency is not captured with a named group then it can be defined in config using this field.
+It will be compiled using Handlebars and the regex `groups` result.
+
### datasourceTemplate
If the `datasource` for a dependency is not captured with a named group then it can be defined in config using this field.
@@ -2180,6 +2190,12 @@ If this setting is true then you would get one PR for webpack@v2 and one for web
If this is set to a non-zero value, _and_ an update contains a release timestamp header, then Renovate will check if the "stability days" have passed.
+Note: Renovate will wait for the set amount of `stabilityDays` to pass for each **separate** version.
+Renovate does not wait until the package has seen no releases for x `stabilityDays`.
+`stabilityDays` is not intended to help with slowing down fast releasing project updates.
+If you want to slow down PRs for a specific package, setup a custom schedule for that package.
+Read [our selective-scheduling help](https://docs.renovatebot.com/noise-reduction/#selective-scheduling) to learn how to set the schedule.
+
If the amount of days since the release is less than the set `stabilityDays` a "pending" status check is added to the branch.
If enough days have passed then the "pending" status is removed, and a "passing" status check is added.
@@ -2196,6 +2212,22 @@ There are a couple of uses for `stabilityDays`:
If you combine `stabilityDays=3` and `prCreation="not-pending"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released.
It's recommended that you enable `dependencyDashboard=true` so you don't lose visibility of these pending PRs.
+#### Prevent holding broken npm packages
+
+npm packages less than 72 hours (3 days) old can be unpublished, which could result in a service impact if you have already updated to it.
+Set `stabilityDays` to 3 for npm packages to prevent relying on a package that can be removed from the registry:
+
+```json
+{
+ "packageRules": [
+ {
+ "matchDatasources": ["npm"],
+ "stabilityDays": 3
+ }
+ ]
+}
+```
+
#### Await X days before Automerging
If you have both `automerge` as well as `stabilityDays` enabled, it means that PRs will be created immediately but automerging will be delayed until X days have passed.
@@ -2250,7 +2282,7 @@ To opt in to letting Renovate update internal package versions normally, set thi
## updateNotScheduled
When schedules are in use, it generally means "no updates".
-However there are cases where updates might be desirable - e.g. if you have configured prCreation=not-pending, or you have rebaseStale=true and the base branch is updated so you want Renovate PRs to be rebased.
+However there are cases where updates might be desirable - e.g. if you have configured prCreation=not-pending, or you have rebaseWhen=behind-base-branch and the base branch is updated so you want Renovate PRs to be rebased.
This defaults to `true`, meaning that Renovate will perform certain "desirable" updates to _existing_ PRs even when outside of schedule.
If you wish to disable all updates outside of scheduled hours then configure this field to `false`.
diff --git a/docs/usage/docker.md b/docs/usage/docker.md
index 38a6cec386e3c0..5146194127eac7 100644
--- a/docs/usage/docker.md
+++ b/docs/usage/docker.md
@@ -206,6 +206,53 @@ module.exports = {
};
```
+#### Google Container Registry
+
+Assume you are running GitLab CI in the Google Cloud, and you are storing your Docker images in the Google Container Registry (GCR).
+
+Access to the GCR uses Bearer token based authentication.
+This token can be obtained by running `gcloud auth print-access-token`, which requires the Google Cloud SDK to be installed.
+
+The token expires after 60 minutes so you cannot store it in a variable for subsequent builds (like you can with `RENOVATE_TOKEN`).
+
+When running Renovate in this context the Google access token must be retrieved and injected into the `hostRules` configuration just before Renovate is started.
+
+_This documentation gives **a few hints** on **a possible way** to achieve this end result._
+
+The basic approach is that you create a custom image and then run Renovate as one of the stages of your project.
+To make this run independent of any user you should use a [`Project Access Token`](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html) (with Scopes: `api`, `read_api` and `write_repository`) for the project and use this as the `RENOVATE_TOKEN` variable for Gitlab CI.
+See also the [renovate-runner repository on GitLab](https://gitlab.com/renovate-bot/renovate-runner) where `.gitlab-ci.yml` configuration examples can be found.
+
+To get access to the token a custom Renovate Docker image is needed that includes the Google Cloud SDK.
+The Dockerfile to create such an image can look like this:
+
+```Dockerfile
+FROM renovate/renovate:25.40.1
+# Include the "Docker tip" which you can find here https://cloud.google.com/sdk/docs/install
+# under "Installation" for "Debian/Ubuntu"
+RUN ...
+```
+
+For Renovate to access the Google Container Registry (GCR) it needs the current Google Access Token.
+The configuration fragment to do that looks something like this:
+
+```js
+hostRules: [
+ {
+ matchHost: 'eu.gcr.io',
+ token: 'MyReallySecretTokenThatExpiresAfter60Minutes',
+ },
+];
+```
+
+One way to provide the short-lived Google Access Token to Renovate is by generating these settings into a `config.js` file from within the `.gitlab-ci.yml` right before starting Renovate:
+
+```yaml
+script:
+ - 'echo "module.exports = { hostRules: [ { matchHost: ''eu.gcr.io'', token: ''"$(gcloud auth print-access-token)"'' } ] };" > config.js'
+ - renovate $RENOVATE_EXTRA_FLAGS
+```
+
#### ChartMuseum
Maybe you're running your own ChartMuseum server to host your private Helm Charts.
diff --git a/docs/usage/gitlab-bot-security.md b/docs/usage/gitlab-bot-security.md
index f4a166d9e63af6..4f7b26f41118e3 100644
--- a/docs/usage/gitlab-bot-security.md
+++ b/docs/usage/gitlab-bot-security.md
@@ -74,7 +74,7 @@ Bot services are better if they are provisioned with a "bot identity" so that us
## Recommended migration
Until the hosted app can be reactivated, we recommend users migrate to use self-hosted pipelines to run Renovate.
-Please see the [renovate-bot/renovate-runner README on GitLab](https://gitlab.com/renovate-bot/renovate-runner/-/blob/master/README.md) for instructions on how to set this up as easily as possible.
+Please see the [renovate-bot/renovate-runner README on GitLab](https://gitlab.com/renovate-bot/renovate-runner/-/blob/HEAD/README.md) for instructions on how to set this up as easily as possible.
The Renovate team is working to find a feasible design for the app so that we can reactive it securely in future.
We welcome any ideas you may have.
diff --git a/docs/usage/node.md b/docs/usage/node.md
index 61a17b7c21027b..a42c285ed2de8b 100644
--- a/docs/usage/node.md
+++ b/docs/usage/node.md
@@ -30,18 +30,18 @@ When Renovate processes your project's repository it will look for the files lis
## Configuring Support Policy
-Renovate supports a [`supportPolicy`](./configuration-options.md#supportpolicy) option that can be passed the following values and associated versions (current as of April 2021):
+Renovate supports a [`supportPolicy`](./configuration-options.md#supportpolicy) option that can be passed the following values and associated versions (current as of June 2021):
**Default:** `lts`
-| supportPolicy | versions | description |
-| ------------- | -------------- | -------------------------------------------------------- |
-| all | 12, 14, 15, 16 | All releases that have not passed their end date |
-| lts | 12, 14 | All releases classified as LTS, including in maintenance |
-| active | 14, 16 | All releases not in maintenance |
-| lts_active | 14 | All releases both LTS and active |
-| lts_latest | 14 | The latest LTS release |
-| current | 16 | The latest release from "all" |
+| supportPolicy | versions | description |
+| ------------- | ---------- | -------------------------------------------------------- |
+| all | 12, 14, 16 | All releases that have not passed their end date |
+| lts | 12, 14 | All releases classified as LTS, including in maintenance |
+| active | 14, 16 | All releases not in maintenance |
+| lts_active | 14 | All releases both LTS and active |
+| lts_latest | 14 | The latest LTS release |
+| current | 16 | The latest release from "all" |
The version numbers associated with each support policy will be updated as new versions of Node.js are released, moved to LTS or maintenance, etc.
diff --git a/docs/usage/nuget.md b/docs/usage/nuget.md
index 607c084a18ff89..8c7fd09143481d 100644
--- a/docs/usage/nuget.md
+++ b/docs/usage/nuget.md
@@ -33,8 +33,8 @@ Alternative feeds can be specified either [in a `NuGet.config` file](https://doc
"nuget": {
"registryUrls": [
"https://api.nuget.org/v3/index.json",
- "http://example1.com/nuget/"
- "http://example2.com/nuget/v3/index.json"
+ "https://example1.com/nuget/",
+ "https://example2.com/nuget/v3/index.json"
]
}
```
diff --git a/docs/usage/private-npm-modules.md b/docs/usage/private-npm-modules.md
index 3eff4152c16027..013128823a2b85 100644
--- a/docs/usage/private-npm-modules.md
+++ b/docs/usage/private-npm-modules.md
@@ -69,7 +69,7 @@ module.exports = {
// https://www.jfrog.com/confluence/display/JFROG/npm+Registry
// Will be passed as `//artifactory.my-company.com/artifactory/api/npm/npm:_auth=` to `.npmrc`
hostType: 'npm',
- matchHost: 'https://artifactory.my-company.com/artifactory/api/npm/npm',
+ matchHost: 'https://artifactory.my-company.com/artifactory/api/npm/npm/',
token: process.env.ARTIFACTORY_NPM_TOKEN,
authType: 'Basic',
},
@@ -77,6 +77,8 @@ module.exports = {
};
```
+**NOTE:** Remember to put a trailing slash at the end of your `matchHost` URL.
+
**NOTE:** Do not use `NPM_TOKEN` as an environment variable.
### Add npmrc string to Renovate config
diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md
index 2f00716900c3b6..39e8b789a159af 100644
--- a/docs/usage/self-hosted-experimental.md
+++ b/docs/usage/self-hosted-experimental.md
@@ -35,6 +35,10 @@ If set to any value, Renovate will always paginate requests to GitHub fully, ins
If set to "false" (string), Renovate will remove any existing `package-lock.json` before attempting to update it.
+## RENOVATE_X_TERRAFORM_LOCK_FILE
+
+If set to any value, Renovate will update Terraform lock files and allow lockfile maintenance.
+
## RENOVATE_USER_AGENT
If set to any string, Renovate will use this as the `user-agent` it sends with HTTP requests.
diff --git a/docs/usage/semantic-commits.md b/docs/usage/semantic-commits.md
index 6d0dbfbbb020ed..82e4bac80d84dd 100644
--- a/docs/usage/semantic-commits.md
+++ b/docs/usage/semantic-commits.md
@@ -15,7 +15,8 @@ When Renovate finds Angular-style commits, Renovate will create commit messages
- chore(deps): update eslint to v4.2.0
- fix(deps): update express to v4.16.2
-Renovate uses `chore` by default, but uses `fix` for updates to your production dependencies in your `package.json` file.
+Renovate uses the `chore` prefix by default.
+When you extend `config:base`, Renovate still defaults to `chore`, but will use the `fix` prefix for npm production dependencies (`devDependencies` still use `chore`).
## Manually enabling or disabling semantic commits
diff --git a/lib/config-validator.ts b/lib/config-validator.ts
index 78350a90ee7c75..bd659398db5ca9 100644
--- a/lib/config-validator.ts
+++ b/lib/config-validator.ts
@@ -4,12 +4,12 @@ import { dequal } from 'dequal';
import { readFileSync } from 'fs-extra';
import JSON5 from 'json5';
import { configFileNames } from './config/app-strings';
-import { getConfig as getFileConfig } from './config/file';
import { massageConfig } from './config/massage';
import { migrateConfig } from './config/migration';
import type { RenovateConfig } from './config/types';
import { validateConfig } from './config/validation';
import { logger } from './logger';
+import { getConfig as getFileConfig } from './workers/global/config/parse/file';
/* eslint-disable no-console */
diff --git a/lib/config/keys/__fixtures__/private.pem b/lib/config/__fixtures__/private.pem
similarity index 100%
rename from lib/config/keys/__fixtures__/private.pem
rename to lib/config/__fixtures__/private.pem
diff --git a/lib/config/admin.ts b/lib/config/admin.ts
index 73f289d2094761..83eb5bdfcae106 100644
--- a/lib/config/admin.ts
+++ b/lib/config/admin.ts
@@ -8,6 +8,7 @@ const repoAdminOptions = [
'allowPostUpgradeCommandTemplating',
'allowScripts',
'allowedPostUpgradeCommands',
+ 'binarySource',
'customEnvVariables',
'dockerChildPrefix',
'dockerImagePrefix',
diff --git a/lib/config/decrypt.spec.ts b/lib/config/decrypt.spec.ts
index 3dfc0b95ad7327..427ca926a411a2 100644
--- a/lib/config/decrypt.spec.ts
+++ b/lib/config/decrypt.spec.ts
@@ -3,7 +3,7 @@ import { setAdminConfig } from './admin';
import { decryptConfig } from './decrypt';
import type { RenovateConfig } from './types';
-const privateKey = loadFixture('private.pem', 'keys');
+const privateKey = loadFixture('private.pem', '.');
describe(getName(), () => {
describe('decryptConfig()', () => {
diff --git a/lib/config/defaults.ts b/lib/config/defaults.ts
index 03df2283a34f85..56bd1a6b4d41ed 100644
--- a/lib/config/defaults.ts
+++ b/lib/config/defaults.ts
@@ -1,5 +1,5 @@
import { getOptions } from './definitions';
-import type { GlobalConfig, RenovateOptions } from './types';
+import type { AllConfig, RenovateOptions } from './types';
const defaultValues = {
boolean: true,
@@ -14,9 +14,9 @@ export function getDefault(option: RenovateOptions): any {
: option.default;
}
-export function getConfig(): GlobalConfig {
+export function getConfig(): AllConfig {
const options = getOptions();
- const config: GlobalConfig = {};
+ const config: AllConfig = {};
options.forEach((option) => {
if (!option.parent) {
config[option.name] = getDefault(option);
diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts
index 8cff7fab64a1b9..daf81de7c8cca1 100644
--- a/lib/config/definitions.ts
+++ b/lib/config/definitions.ts
@@ -4,7 +4,6 @@ import { getPlatformList } from '../platform';
import { getVersioningList } from '../versioning';
import * as dockerVersioning from '../versioning/docker';
import * as pep440Versioning from '../versioning/pep440';
-import * as semverVersioning from '../versioning/semver';
import type { RenovateOptions } from './types';
const options: RenovateOptions[] = [
@@ -710,7 +709,6 @@ const options: RenovateOptions[] = [
description: 'Versioning to use for filtering and comparisons.',
type: 'string',
allowedValues: getVersioningList(),
- default: semverVersioning.id,
cli: false,
env: false,
},
@@ -1929,6 +1927,15 @@ const options: RenovateOptions[] = [
cli: false,
env: false,
},
+ {
+ name: 'currentValueTemplate',
+ description:
+ 'Optional currentValue for extracted dependencies. Valid only within a `regexManagers` object.',
+ type: 'string',
+ parent: 'regexManagers',
+ cli: false,
+ env: false,
+ },
{
name: 'versioningTemplate',
description:
@@ -1947,6 +1954,15 @@ const options: RenovateOptions[] = [
cli: false,
env: false,
},
+ {
+ name: 'extractVersionTemplate',
+ description:
+ 'Optional extractVersion for extracted dependencies. Valid only within a `regexManagers` object.',
+ type: 'string',
+ parent: 'regexManagers',
+ cli: false,
+ env: false,
+ },
{
name: 'fetchReleaseNotes',
description: 'Allow to disable release notes fetching.',
diff --git a/lib/config/index.spec.ts b/lib/config/index.spec.ts
index c716f5a4737d92..73744b497e91e2 100644
--- a/lib/config/index.spec.ts
+++ b/lib/config/index.spec.ts
@@ -1,7 +1,4 @@
-import upath from 'upath';
import { getName } from '../../test/util';
-import { readFile } from '../util/fs';
-import getArgv from './config/__fixtures__/argv';
import { getConfig } from './defaults';
jest.mock('../datasource/npm');
@@ -14,111 +11,6 @@ try {
const defaultConfig = getConfig();
describe(getName(), () => {
- describe('.parseConfigs(env, defaultArgv)', () => {
- let configParser: typeof import('.');
- let defaultArgv: string[];
- let defaultEnv: NodeJS.ProcessEnv;
- beforeEach(async () => {
- jest.resetModules();
- configParser = await import('./index');
- defaultArgv = getArgv();
- defaultEnv = { RENOVATE_CONFIG_FILE: 'abc' };
- jest.mock('delay', () => Promise.resolve());
- });
- it('supports token in env', async () => {
- const env: NodeJS.ProcessEnv = { ...defaultEnv, RENOVATE_TOKEN: 'abc' };
- const parsedConfig = await configParser.parseConfigs(env, defaultArgv);
- expect(parsedConfig).toContainEntries([['token', 'abc']]);
- });
-
- it('supports token in CLI options', async () => {
- defaultArgv = defaultArgv.concat([
- '--token=abc',
- '--pr-footer=custom',
- '--log-context=abc123',
- ]);
- const parsedConfig = await configParser.parseConfigs(
- defaultEnv,
- defaultArgv
- );
- expect(parsedConfig).toContainEntries([
- ['token', 'abc'],
- ['prFooter', 'custom'],
- ['logContext', 'abc123'],
- ]);
- });
-
- it('supports forceCli', async () => {
- defaultArgv = defaultArgv.concat(['--force-cli=false']);
- const env: NodeJS.ProcessEnv = {
- ...defaultEnv,
- RENOVATE_TOKEN: 'abc',
- };
- const parsedConfig = await configParser.parseConfigs(env, defaultArgv);
- expect(parsedConfig).toContainEntries([
- ['token', 'abc'],
- ['force', null],
- ]);
- expect(parsedConfig).not.toContainKey('configFile');
- });
- it('supports config.force', async () => {
- const configPath = upath.join(
- __dirname,
- 'config/__fixtures__/with-force.js'
- );
- const env: NodeJS.ProcessEnv = {
- ...defaultEnv,
- RENOVATE_CONFIG_FILE: configPath,
- };
- const parsedConfig = await configParser.parseConfigs(env, defaultArgv);
- expect(parsedConfig).toContainEntries([
- ['token', 'abcdefg'],
- [
- 'force',
- {
- schedule: null,
- },
- ],
- ]);
- });
- it('reads private key from file', async () => {
- const privateKeyPath = upath.join(
- __dirname,
- 'keys/__fixtures__/private.pem'
- );
- const env: NodeJS.ProcessEnv = {
- ...defaultEnv,
- RENOVATE_PRIVATE_KEY_PATH: privateKeyPath,
- };
- const expected = await readFile(privateKeyPath);
- const parsedConfig = await configParser.parseConfigs(env, defaultArgv);
-
- expect(parsedConfig).toContainEntries([['privateKey', expected]]);
- });
- it('supports Bitbucket username/passwod', async () => {
- defaultArgv = defaultArgv.concat([
- '--platform=bitbucket',
- '--username=user',
- '--password=pass',
- ]);
- const parsedConfig = await configParser.parseConfigs(
- defaultEnv,
- defaultArgv
- );
- expect(parsedConfig).toContainEntries([
- ['platform', 'bitbucket'],
- ['username', 'user'],
- ['password', 'pass'],
- ]);
- });
- it('massages trailing slash into endpoint', async () => {
- defaultArgv = defaultArgv.concat([
- '--endpoint=https://github.renovatebot.com/api/v3',
- ]);
- const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
- expect(parsed.endpoint).toEqual('https://github.renovatebot.com/api/v3/');
- });
- });
describe('mergeChildConfig(parentConfig, childConfig)', () => {
it('merges', async () => {
const parentConfig = { ...defaultConfig };
diff --git a/lib/config/index.ts b/lib/config/index.ts
index 366813c25a0c80..9cd401aca6689f 100644
--- a/lib/config/index.ts
+++ b/lib/config/index.ts
@@ -1,14 +1,8 @@
-import { addStream, logger, setContext } from '../logger';
+import { logger } from '../logger';
import { get, getLanguageList, getManagerList } from '../manager';
-import { ensureDir, getSubDirectory, readFile } from '../util/fs';
-import { ensureTrailingSlash } from '../util/url';
-import * as cliParser from './cli';
-import * as defaultsParser from './defaults';
import * as definitions from './definitions';
-import * as envParser from './env';
-import * as fileParser from './file';
import type {
- GlobalConfig,
+ AllConfig,
ManagerConfig,
RenovateConfig,
RenovateConfigStage,
@@ -39,89 +33,10 @@ export function getManagerConfig(
return managerConfig;
}
-export async function parseConfigs(
- env: NodeJS.ProcessEnv,
- argv: string[]
-): Promise {
- logger.debug('Parsing configs');
-
- // Get configs
- const defaultConfig = defaultsParser.getConfig();
- const fileConfig = fileParser.getConfig(env);
- const cliConfig = cliParser.getConfig(argv);
- const envConfig = envParser.getConfig(env);
-
- let config: GlobalConfig = mergeChildConfig(fileConfig, envConfig);
- config = mergeChildConfig(config, cliConfig);
-
- const combinedConfig = config;
-
- config = mergeChildConfig(defaultConfig, config);
-
- if (config.forceCli) {
- const forcedCli = { ...cliConfig };
- delete forcedCli.token;
- delete forcedCli.hostRules;
- if (config.force) {
- config.force = { ...config.force, ...forcedCli };
- } else {
- config.force = forcedCli;
- }
- }
-
- if (!config.privateKey && config.privateKeyPath) {
- config.privateKey = await readFile(config.privateKeyPath);
- delete config.privateKeyPath;
- }
-
- if (config.logContext) {
- // This only has an effect if logContext was defined via file or CLI, otherwise it would already have been detected in env
- setContext(config.logContext);
- }
-
- // Add file logger
- // istanbul ignore if
- if (config.logFile) {
- logger.debug(
- `Enabling ${config.logFileLevel} logging to ${config.logFile}`
- );
- await ensureDir(getSubDirectory(config.logFile));
- addStream({
- name: 'logfile',
- path: config.logFile,
- level: config.logFileLevel,
- });
- }
-
- logger.trace({ config: defaultConfig }, 'Default config');
- logger.debug({ config: fileConfig }, 'File config');
- logger.debug({ config: cliConfig }, 'CLI config');
- logger.debug({ config: envConfig }, 'Env config');
- logger.debug({ config: combinedConfig }, 'Combined config');
-
- // Get global config
- logger.trace({ config }, 'Full config');
-
- // Print config
- logger.trace({ config }, 'Global config');
-
- // Massage endpoint to have a trailing slash
- if (config.endpoint) {
- logger.debug('Adding trailing slash to endpoint');
- config.endpoint = ensureTrailingSlash(config.endpoint);
- }
-
- // Remove log file entries
- delete config.logFile;
- delete config.logFileLevel;
-
- return config;
-}
-
export function filterConfig(
- inputConfig: GlobalConfig,
+ inputConfig: AllConfig,
targetStage: RenovateConfigStage
-): GlobalConfig {
+): AllConfig {
logger.trace({ config: inputConfig }, `filterConfig('${targetStage}')`);
const outputConfig: RenovateConfig = { ...inputConfig };
const stages = ['global', 'repository', 'package', 'branch', 'pr'];
diff --git a/lib/config/presets/__snapshots__/index.spec.ts.snap b/lib/config/presets/__snapshots__/index.spec.ts.snap
index fa82ef87f41827..a94385764e81d8 100644
--- a/lib/config/presets/__snapshots__/index.spec.ts.snap
+++ b/lib/config/presets/__snapshots__/index.spec.ts.snap
@@ -67,9 +67,21 @@ exports[`config/presets/index getPreset handles preset not found 3`] = `undefine
exports[`config/presets/index getPreset handles removed presets with a migration 1`] = `
Object {
- "dependencyDashboard": true,
- "description": Array [
- "Enable Renovate Dependency Dashboard creation",
+ "extends": Array [
+ ":separateMajorReleases",
+ ":combinePatchMinorReleases",
+ ":ignoreUnstable",
+ ":prImmediately",
+ ":semanticPrefixFixDepsChoreOthers",
+ ":updateNotScheduled",
+ ":automergeDisabled",
+ ":ignoreModulesAndTests",
+ ":autodetectPinVersions",
+ ":prHourlyLimit2",
+ ":prConcurrentLimit20",
+ "group:monorepos",
+ "group:recommended",
+ "workarounds:all",
],
}
`;
@@ -384,6 +396,7 @@ Object {
exports[`config/presets/index resolvePreset combines two package alls 1`] = `
Object {
"matchPackageNames": Array [
+ "@types/eslint",
"babel-eslint",
],
"matchPackagePrefixes": Array [
@@ -473,6 +486,7 @@ Object {
exports[`config/presets/index resolvePreset resolves eslint 1`] = `
Object {
"matchPackageNames": Array [
+ "@types/eslint",
"babel-eslint",
],
"matchPackagePrefixes": Array [
@@ -488,6 +502,7 @@ Object {
"All lint-related packages",
],
"matchPackageNames": Array [
+ "@types/eslint",
"babel-eslint",
"codelyzer",
"remark-lint",
@@ -516,6 +531,7 @@ Object {
"All lint-related packages",
],
"matchPackageNames": Array [
+ "@types/eslint",
"babel-eslint",
"codelyzer",
"remark-lint",
@@ -540,6 +556,7 @@ Object {
Object {
"groupName": "eslint",
"matchPackageNames": Array [
+ "@types/eslint",
"babel-eslint",
],
"matchPackagePrefixes": Array [
diff --git a/lib/config/presets/bitbucket-server/index.spec.ts b/lib/config/presets/bitbucket-server/index.spec.ts
index 4aaad95f332645..87bae5b5d7cd2e 100644
--- a/lib/config/presets/bitbucket-server/index.spec.ts
+++ b/lib/config/presets/bitbucket-server/index.spec.ts
@@ -13,12 +13,9 @@ const basePath = '/rest/api/1.0/projects/some/repos/repo/browse';
describe(getName(), () => {
beforeEach(() => {
- httpMock.setup();
hostRules.find.mockReturnValue({ token: 'abc' });
});
- afterEach(() => httpMock.reset());
-
describe('fetchJSONFile()', () => {
it('returns JSON', async () => {
httpMock
diff --git a/lib/config/presets/bitbucket/index.spec.ts b/lib/config/presets/bitbucket/index.spec.ts
index cf26c9410dbfcd..7a69e16f7e6cbe 100644
--- a/lib/config/presets/bitbucket/index.spec.ts
+++ b/lib/config/presets/bitbucket/index.spec.ts
@@ -14,14 +14,6 @@ describe(getName(), () => {
setPlatformApi('bitbucket');
});
- beforeEach(() => {
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
- });
-
describe('fetchJSONFile()', () => {
it('returns JSON', async () => {
const data = { foo: 'bar' };
diff --git a/lib/config/presets/gitea/index.spec.ts b/lib/config/presets/gitea/index.spec.ts
index 5582056521e852..1e706c652686f6 100644
--- a/lib/config/presets/gitea/index.spec.ts
+++ b/lib/config/presets/gitea/index.spec.ts
@@ -14,13 +14,10 @@ const basePath = '/repos/some/repo/contents';
describe(getName(), () => {
beforeEach(() => {
- httpMock.setup();
hostRules.find.mockReturnValue({ token: 'abc' });
setBaseUrl(giteaApiHost);
});
- afterEach(() => httpMock.reset());
-
describe('fetchJSONFile()', () => {
it('returns JSON', async () => {
httpMock
diff --git a/lib/config/presets/github/index.spec.ts b/lib/config/presets/github/index.spec.ts
index 9e2fffdca405e8..e7b45b1684122d 100644
--- a/lib/config/presets/github/index.spec.ts
+++ b/lib/config/presets/github/index.spec.ts
@@ -13,12 +13,9 @@ const basePath = '/repos/some/repo/contents';
describe(getName(), () => {
beforeEach(() => {
- httpMock.setup();
hostRules.find.mockReturnValue({ token: 'abc' });
});
- afterEach(() => httpMock.reset());
-
describe('fetchJSONFile()', () => {
it('returns JSON', async () => {
httpMock
diff --git a/lib/config/presets/gitlab/index.spec.ts b/lib/config/presets/gitlab/index.spec.ts
index 53d32d88bf09f5..b100dc70f5efcb 100644
--- a/lib/config/presets/gitlab/index.spec.ts
+++ b/lib/config/presets/gitlab/index.spec.ts
@@ -10,11 +10,6 @@ const basePath = '/api/v4/projects/some%2Frepo/repository';
describe(getName(), () => {
beforeEach(() => {
jest.resetAllMocks();
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
describe('getPreset()', () => {
diff --git a/lib/config/presets/index.spec.ts b/lib/config/presets/index.spec.ts
index 763cf30f19335e..912b3d8a89ab96 100644
--- a/lib/config/presets/index.spec.ts
+++ b/lib/config/presets/index.spec.ts
@@ -188,7 +188,7 @@ describe(getName(), () => {
config.extends = ['packages:linters'];
const res = await presets.resolveConfigPresets(config);
expect(res).toMatchSnapshot();
- expect(res.matchPackageNames).toHaveLength(3);
+ expect(res.matchPackageNames).toHaveLength(4);
expect(res.matchPackagePatterns).toHaveLength(1);
expect(res.matchPackagePrefixes).toHaveLength(4);
});
@@ -198,7 +198,7 @@ describe(getName(), () => {
expect(res).toMatchSnapshot();
const rule = res.packageRules[0];
expect(rule.automerge).toBe(true);
- expect(rule.matchPackageNames).toHaveLength(3);
+ expect(rule.matchPackageNames).toHaveLength(4);
expect(rule.matchPackagePatterns).toHaveLength(1);
expect(rule.matchPackagePrefixes).toHaveLength(4);
});
@@ -394,7 +394,7 @@ describe(getName(), () => {
});
describe('getPreset', () => {
it('handles removed presets with a migration', async () => {
- const res = await presets.getPreset(':masterIssue', {});
+ const res = await presets.getPreset(':base', {});
expect(res).toMatchSnapshot();
});
it('handles removed presets with no migration', async () => {
diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts
index edde57d982dbe6..d3c6bde5a36b95 100644
--- a/lib/config/presets/index.ts
+++ b/lib/config/presets/index.ts
@@ -8,7 +8,7 @@ import { ExternalHostError } from '../../types/errors/external-host-error';
import { regEx } from '../../util/regex';
import * as massage from '../massage';
import * as migration from '../migration';
-import type { GlobalConfig, RenovateConfig } from '../types';
+import type { AllConfig, RenovateConfig } from '../types';
import { mergeChildConfig } from '../utils';
import { removedPresets } from './common';
import * as gitea from './gitea';
@@ -222,11 +222,11 @@ export async function getPreset(
}
export async function resolveConfigPresets(
- inputConfig: GlobalConfig,
+ inputConfig: AllConfig,
baseConfig?: RenovateConfig,
ignorePresets?: string[],
existingPresets: string[] = []
-): Promise {
+): Promise {
if (!ignorePresets || ignorePresets.length === 0) {
ignorePresets = inputConfig.ignorePresets || []; // eslint-disable-line
}
@@ -234,7 +234,7 @@ export async function resolveConfigPresets(
{ config: inputConfig, existingPresets },
'resolveConfigPresets'
);
- let config: GlobalConfig = {};
+ let config: AllConfig = {};
// First, merge all the preset configs from left to right
if (inputConfig.extends?.length) {
for (const preset of inputConfig.extends) {
diff --git a/lib/config/presets/internal/monorepo.ts b/lib/config/presets/internal/monorepo.ts
index 16b6a696aab30f..5de779de0f1406 100644
--- a/lib/config/presets/internal/monorepo.ts
+++ b/lib/config/presets/internal/monorepo.ts
@@ -25,6 +25,7 @@ const repoGroups = {
'azure azure-storage-net': 'https://github.com/Azure/azure-storage-net',
'bugsnag-js': 'https://github.com/bugsnag/bugsnag-js',
'chakra-ui': 'https://github.com/chakra-ui/chakra-ui',
+ 'contentful-rich-text': 'https://github.com/contentful/rich-text',
'date-io': 'https://github.com/dmtrKovalenko/date-io',
'devextreme-reactive': 'https://github.com/DevExpress/devextreme-reactive',
'electron-forge': 'https://github.com/electron-userland/electron-forge',
@@ -42,9 +43,11 @@ const repoGroups = {
'reactivestack-cookies': 'https://github.com/reactivestack/cookies',
'reg-suit': 'https://github.com/reg-viz/reg-suit',
'semantic-release': 'https://github.com/semantic-release/',
+ 'shopify-app-bridge': 'https://github.com/Shopify/app-bridge',
'System.IO.Abstractions':
'https://github.com/System-IO-Abstractions/System.IO.Abstractions/',
'telus-tds': 'https://github.com/telusdigital/tds',
+ 'theme-ui': 'https://github.com/system-ui/theme-ui',
'typescript-eslint': 'https://github.com/typescript-eslint/typescript-eslint',
'typography-js': 'https://github.com/KyleAMathews/typography.js',
'vue-cli': 'https://github.com/vuejs/vue-cli',
@@ -76,6 +79,7 @@ const repoGroups = {
feathers: 'https://github.com/feathersjs/feathers',
fimbullinter: 'https://github.com/fimbullinter/wotan',
flopflip: 'https://github.com/tdeekens/flopflip',
+ Fontsource: 'https://github.com/fontsource/fontsource',
formatjs: 'https://github.com/formatjs/formatjs',
framework7: 'https://github.com/framework7io/framework7',
gatsby: 'https://github.com/gatsbyjs/gatsby',
@@ -121,6 +125,7 @@ const repoGroups = {
openfeign: 'https://github.com/OpenFeign/feign',
opentelemetry: 'https://github.com/open-telemetry/opentelemetry-js',
OpenTelemetryDotnet: 'https://github.com/open-telemetry/opentelemetry-dotnet',
+ orleans: 'https://github.com/dotnet/orleans',
picasso: 'https://github.com/qlik-oss/picasso.js',
pnpjs: 'https://github.com/pnp/pnpjs',
playwright: 'https://github.com/Microsoft/playwright',
diff --git a/lib/config/presets/internal/packages.ts b/lib/config/presets/internal/packages.ts
index 7828a52d9fece0..7cc856ed63ab33 100644
--- a/lib/config/presets/internal/packages.ts
+++ b/lib/config/presets/internal/packages.ts
@@ -24,7 +24,7 @@ export const presets: Record = {
},
eslint: {
description: 'All eslint packages',
- matchPackageNames: ['babel-eslint'],
+ matchPackageNames: ['@types/eslint', 'babel-eslint'],
matchPackagePrefixes: ['@typescript-eslint/', 'eslint'],
},
stylelint: {
@@ -58,6 +58,7 @@ export const presets: Record = {
'ember-exam',
'ember-mocha',
'ember-qunit',
+ 'enzyme',
'istanbul',
'mock-fs',
'nock',
@@ -65,7 +66,15 @@ export const presets: Record = {
'proxyquire',
'supertest',
],
- matchPackagePrefixes: ['chai', 'jest', 'mocha', 'qunit', 'sinon', 'should'],
+ matchPackagePrefixes: [
+ '@testing-library',
+ 'chai',
+ 'jest',
+ 'mocha',
+ 'qunit',
+ 'should',
+ 'sinon',
+ ],
},
unitTest: {
description: 'All unit test packages',
diff --git a/lib/config/presets/npm/index.spec.ts b/lib/config/presets/npm/index.spec.ts
index ea2633e5002be2..1e251a9f6aedce 100644
--- a/lib/config/presets/npm/index.spec.ts
+++ b/lib/config/presets/npm/index.spec.ts
@@ -1,4 +1,4 @@
-import nock from 'nock';
+import * as httpMock from '../../../../test/http-mock';
import { getName } from '../../../../test/util';
import { setAdminConfig } from '../../admin';
import * as npm from '.';
@@ -10,13 +10,12 @@ describe(getName(), () => {
beforeEach(() => {
jest.resetAllMocks();
setAdminConfig();
- nock.cleanAll();
});
afterEach(() => {
delete process.env.RENOVATE_CACHE_NPM_MINUTES;
});
it('should throw if no package', async () => {
- nock('https://registry.npmjs.org').get('/nopackage').reply(404);
+ httpMock.scope('https://registry.npmjs.org').get('/nopackage').reply(404);
await expect(
npm.getPreset({ packageName: 'nopackage', presetName: 'default' })
).rejects.toThrow(/dep not found/);
@@ -45,7 +44,8 @@ describe(getName(), () => {
'0.0.2': '2018-05-07T07:21:53+02:00',
},
};
- nock('https://registry.npmjs.org')
+ httpMock
+ .scope('https://registry.npmjs.org')
.get('/norenovateconfig')
.reply(200, presetPackage);
await expect(
@@ -77,7 +77,8 @@ describe(getName(), () => {
'0.0.2': '2018-05-07T07:21:53+02:00',
},
};
- nock('https://registry.npmjs.org')
+ httpMock
+ .scope('https://registry.npmjs.org')
.get('/presetnamenotfound')
.reply(200, presetPackage);
await expect(
@@ -112,7 +113,8 @@ describe(getName(), () => {
'0.0.2': '2018-05-07T07:21:53+02:00',
},
};
- nock('https://registry.npmjs.org')
+ httpMock
+ .scope('https://registry.npmjs.org')
.get('/workingpreset')
.reply(200, presetPackage);
const res = await npm.getPreset({ packageName: 'workingpreset' });
diff --git a/lib/config/secrets.ts b/lib/config/secrets.ts
index c07a0fc912cab3..c9eee725a6de0f 100644
--- a/lib/config/secrets.ts
+++ b/lib/config/secrets.ts
@@ -6,7 +6,7 @@ import {
import { logger } from '../logger';
import { regEx } from '../util/regex';
import { add } from '../util/sanitize';
-import { GlobalConfig, RenovateConfig } from './types';
+import { AllConfig, RenovateConfig } from './types';
const secretNamePattern = '[A-Za-z][A-Za-z0-9_]*';
@@ -40,7 +40,7 @@ function validateSecrets(secrets_: unknown): void {
}
}
-export function validateConfigSecrets(config: GlobalConfig): void {
+export function validateConfigSecrets(config: AllConfig): void {
validateSecrets(config.secrets);
if (config.repositories) {
for (const repository of config.repositories) {
@@ -113,12 +113,15 @@ function replaceSecretsinObject(
return config;
}
-export function applySecretsToConfig(config: RenovateConfig): RenovateConfig {
+export function applySecretsToConfig(
+ config: RenovateConfig,
+ secrets = config.secrets
+): RenovateConfig {
// Add all secrets to be sanitized
- if (is.plainObject(config.secrets)) {
- for (const secret of Object.values(config.secrets)) {
+ if (is.plainObject(secrets)) {
+ for (const secret of Object.values(secrets)) {
add(String(secret));
}
}
- return replaceSecretsinObject(config, config.secrets);
+ return replaceSecretsinObject(config, secrets);
}
diff --git a/lib/config/types.ts b/lib/config/types.ts
index bd56a58505b444..042826e1aeaf99 100644
--- a/lib/config/types.ts
+++ b/lib/config/types.ts
@@ -90,6 +90,7 @@ export interface RepoAdminConfig {
allowPostUpgradeCommandTemplating?: boolean;
allowScripts?: boolean;
allowedPostUpgradeCommands?: string[];
+ binarySource?: 'docker' | 'global';
customEnvVariables?: Record;
dockerChildPrefix?: string;
dockerImagePrefix?: string;
@@ -200,7 +201,7 @@ export interface RenovateConfig
secrets?: Record;
}
-export interface GlobalConfig extends RenovateConfig, GlobalOnlyConfig {}
+export interface AllConfig extends RenovateConfig, GlobalOnlyConfig {}
export interface AssigneesAndReviewersConfig {
assigneesFromCodeOwners?: boolean;
@@ -369,10 +370,6 @@ export interface ManagerConfig extends RenovateConfig {
manager: string;
}
-export interface RenovateCliConfig extends Record {
- repositories?: string[];
-}
-
export interface MigratedConfig {
isMigrated: boolean;
migratedConfig: RenovateConfig;
diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts
index eb7eb914b4f317..886fe778380b0b 100644
--- a/lib/config/validation.spec.ts
+++ b/lib/config/validation.spec.ts
@@ -352,6 +352,7 @@ describe(getName(), () => {
depNameTemplate: 'foo',
datasourceTemplate: 'bar',
registryUrlTemplate: 'foobar',
+ extractVersionTemplate: '^(?v\\d+\\.\\d+)',
},
],
};
diff --git a/lib/config/validation.ts b/lib/config/validation.ts
index 9e876c1b9ab0d4..2cf5b2adc4d269 100644
--- a/lib/config/validation.ts
+++ b/lib/config/validation.ts
@@ -387,6 +387,8 @@ export async function validateConfig(
'datasourceTemplate',
'versioningTemplate',
'registryUrlTemplate',
+ 'currentValueTemplate',
+ 'extractVersionTemplate',
];
// TODO: fix types
for (const regexManager of val as any[]) {
@@ -514,7 +516,9 @@ export async function validateConfig(
message: `Invalid \`${currentPath}.${key}.${res}\` configuration: value is not a url`,
});
}
- } else if (['customEnvVariables', 'migratePresets'].includes(key)) {
+ } else if (
+ ['customEnvVariables', 'migratePresets', 'secrets'].includes(key)
+ ) {
const res = validatePlainObject(val);
if (res !== true) {
errors.push({
diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts
index 02d5ac1242fafd..14eecf92eeba6b 100644
--- a/lib/datasource/api.ts
+++ b/lib/datasource/api.ts
@@ -4,22 +4,22 @@ import { ClojureDatasource } from './clojure';
import * as crate from './crate';
import { DartDatasource } from './dart';
import * as docker from './docker';
-import * as galaxy from './galaxy';
-import * as galaxyCollection from './galaxy-collection';
+import { GalaxyDatasource } from './galaxy';
+import { GalaxyCollectionDatasource } from './galaxy-collection';
import * as gitRefs from './git-refs';
import * as gitTags from './git-tags';
import * as githubReleases from './github-releases';
import * as githubTags from './github-tags';
import * as gitlabTags from './gitlab-tags';
import * as go from './go';
-import * as gradleVersion from './gradle-version';
-import * as helm from './helm';
+import { GradleVersionDatasource } from './gradle-version';
+import { HelmDatasource } from './helm';
import * as hex from './hex';
import * as jenkinsPlugins from './jenkins-plugins';
import * as maven from './maven';
import * as npm from './npm';
import * as nuget from './nuget';
-import * as orb from './orb';
+import { OrbDatasource } from './orb';
import * as packagist from './packagist';
import * as pod from './pod';
import * as pypi from './pypi';
@@ -29,7 +29,7 @@ import * as rubygems from './rubygems';
import * as sbtPackage from './sbt-package';
import * as sbtPlugin from './sbt-plugin';
import * as terraformModule from './terraform-module';
-import * as terraformProvider from './terraform-provider';
+import { TerraformProviderDatasource } from './terraform-provider';
import type { DatasourceApi } from './types';
const api = new Map();
@@ -41,22 +41,22 @@ api.set('clojure', new ClojureDatasource());
api.set('crate', crate);
api.set('dart', new DartDatasource());
api.set('docker', docker);
-api.set('galaxy', galaxy);
-api.set('galaxy-collection', galaxyCollection);
+api.set('galaxy', new GalaxyDatasource());
+api.set('galaxy-collection', new GalaxyCollectionDatasource());
api.set('git-refs', gitRefs);
api.set('git-tags', gitTags);
api.set('github-releases', githubReleases);
api.set('github-tags', githubTags);
api.set('gitlab-tags', gitlabTags);
api.set('go', go);
-api.set('gradle-version', gradleVersion);
-api.set('helm', helm);
+api.set('gradle-version', new GradleVersionDatasource());
+api.set('helm', new HelmDatasource());
api.set('hex', hex);
api.set('jenkins-plugins', jenkinsPlugins);
api.set('maven', maven);
api.set('npm', npm);
api.set('nuget', nuget);
-api.set('orb', orb);
+api.set('orb', new OrbDatasource());
api.set('packagist', packagist);
api.set('pod', pod);
api.set('pypi', pypi);
@@ -66,4 +66,4 @@ api.set('rubygems', rubygems);
api.set('sbt-package', sbtPackage);
api.set('sbt-plugin', sbtPlugin);
api.set('terraform-module', terraformModule);
-api.set('terraform-provider', terraformProvider);
+api.set('terraform-provider', new TerraformProviderDatasource());
diff --git a/lib/datasource/bitbucket-tags/index.spec.ts b/lib/datasource/bitbucket-tags/index.spec.ts
index ab7b800cf490c5..83290ddd9b5357 100644
--- a/lib/datasource/bitbucket-tags/index.spec.ts
+++ b/lib/datasource/bitbucket-tags/index.spec.ts
@@ -4,13 +4,6 @@ import { getName } from '../../../test/util';
import { id as datasource } from '.';
describe(getName(), () => {
- beforeEach(() => {
- httpMock.reset();
- httpMock.setup();
- });
- afterEach(() => {
- httpMock.reset();
- });
describe('getReleases', () => {
it('returns tags from bitbucket cloud', async () => {
const body = {
diff --git a/lib/datasource/cdnjs/index.spec.ts b/lib/datasource/cdnjs/index.spec.ts
index d81e627fbc4d5c..c7aed56bcd1581 100644
--- a/lib/datasource/cdnjs/index.spec.ts
+++ b/lib/datasource/cdnjs/index.spec.ts
@@ -16,11 +16,6 @@ describe(getName(), () => {
describe('getReleases', () => {
beforeEach(() => {
jest.clearAllMocks();
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
it('throws for empty result', async () => {
diff --git a/lib/datasource/clojure/__snapshots__/index.spec.ts.snap b/lib/datasource/clojure/__snapshots__/index.spec.ts.snap
index 20e9cbbd5f7794..81e923cf1bd916 100644
--- a/lib/datasource/clojure/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/clojure/__snapshots__/index.spec.ts.snap
@@ -640,40 +640,3 @@ Array [
},
]
`;
-
-exports[`datasource/clojure/index supports file protocol 1`] = `
-Object {
- "display": "org.example:package",
- "group": "org.example",
- "homepage": "https://package.example.org/about",
- "name": "package",
- "registryUrl": "file:///bar",
- "releases": Array [
- Object {
- "version": "1.0.0",
- },
- Object {
- "version": "1.0.1",
- },
- Object {
- "version": "1.0.2",
- },
- Object {
- "version": "2.0.0",
- },
- ],
-}
-`;
-
-exports[`datasource/clojure/index supports file protocol 2`] = `
-Array [
- Array [
- "/bar/org/example/package/maven-metadata.xml",
- "utf8",
- ],
- Array [
- "/bar/org/example/package/2.0.0/package-2.0.0.pom",
- "utf8",
- ],
-]
-`;
diff --git a/lib/datasource/clojure/index.spec.ts b/lib/datasource/clojure/index.spec.ts
index 5285ec397c6ce0..546bb2e44192b3 100644
--- a/lib/datasource/clojure/index.spec.ts
+++ b/lib/datasource/clojure/index.spec.ts
@@ -1,15 +1,11 @@
-import _fs from 'fs-extra';
import upath from 'upath';
import { ReleaseResult, getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
-import { getName, loadFixture, mocked } from '../../../test/util';
+import { getName, loadFixture } from '../../../test/util';
import * as hostRules from '../../util/host-rules';
import { id as versioning } from '../../versioning/maven';
import { ClojureDatasource } from '.';
-jest.mock('fs-extra');
-const fs = mocked(_fs);
-
const baseUrl = 'https://clojars.org/repo';
const baseUrlCustom = 'https://custom.registry.renovatebot.com';
@@ -90,12 +86,10 @@ describe(getName(), () => {
token: 'abc123',
});
jest.resetAllMocks();
- httpMock.setup();
});
afterEach(() => {
hostRules.clear();
- httpMock.reset();
delete process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK;
});
@@ -243,23 +237,4 @@ describe(getName(), () => {
expect(sourceUrl).toEqual('https://github.com/example/test');
});
-
- it('supports file protocol', async () => {
- fs.exists.mockResolvedValueOnce(false);
-
- fs.exists.mockResolvedValueOnce(true);
- fs.readFile.mockResolvedValueOnce(
- Buffer.from(loadFixture('metadata.xml', upath.join('..', 'maven')))
- );
-
- fs.exists.mockResolvedValueOnce(true);
- fs.readFile.mockResolvedValueOnce(
- Buffer.from(loadFixture('pom.xml', upath.join('..', 'maven')))
- );
-
- const res = await get('org.example:package', 'file:///foo', 'file:///bar');
-
- expect(res).toMatchSnapshot();
- expect(fs.readFile.mock.calls).toMatchSnapshot();
- });
});
diff --git a/lib/datasource/crate/index.spec.ts b/lib/datasource/crate/index.spec.ts
index 00ae5986f2c7cb..b62822b12eb36b 100644
--- a/lib/datasource/crate/index.spec.ts
+++ b/lib/datasource/crate/index.spec.ts
@@ -78,8 +78,6 @@ describe(getName(), () => {
let adminConfig: RepoAdminConfig;
beforeEach(async () => {
- httpMock.setup();
-
tmpDir = await dir();
adminConfig = {
@@ -96,8 +94,6 @@ describe(getName(), () => {
fs.rmdirSync(tmpDir.path, { recursive: true });
tmpDir = null;
setAdminConfig();
-
- httpMock.reset();
});
it('returns null for missing registry url', async () => {
diff --git a/lib/datasource/dart/index.spec.ts b/lib/datasource/dart/index.spec.ts
index 0d14f99c40f268..286ac8767dd682 100644
--- a/lib/datasource/dart/index.spec.ts
+++ b/lib/datasource/dart/index.spec.ts
@@ -8,14 +8,6 @@ const body = loadJsonFixture('shared_preferences.json');
const baseUrl = 'https://pub.dartlang.org/api/packages/';
describe(getName(), () => {
- beforeEach(() => {
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
- });
-
describe('getReleases', () => {
it('returns null for empty result', async () => {
httpMock.scope(baseUrl).get('/non_sense').reply(200, null);
diff --git a/lib/datasource/datasource.spec.ts b/lib/datasource/datasource.spec.ts
index 1df40a4d1e9fb8..6a1255ff2800de 100644
--- a/lib/datasource/datasource.spec.ts
+++ b/lib/datasource/datasource.spec.ts
@@ -24,14 +24,6 @@ class TestDatasource extends Datasource {
}
describe(getName(), () => {
- beforeEach(() => {
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
- });
-
it('should throw on 429', async () => {
const testDatasource = new TestDatasource();
diff --git a/lib/datasource/docker/__snapshots__/index.spec.ts.snap b/lib/datasource/docker/__snapshots__/index.spec.ts.snap
index be528e281181c5..21c6a14bd9010b 100644
--- a/lib/datasource/docker/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/docker/__snapshots__/index.spec.ts.snap
@@ -773,7 +773,26 @@ Array [
exports[`datasource/docker/index getReleases supports labels 1`] = `
Object {
"registryUrl": "https://index.docker.io",
- "releases": Array [],
+ "releases": Array [
+ Object {
+ "version": "1.0.0",
+ },
+ Object {
+ "version": "1.2.3-alpine",
+ },
+ Object {
+ "version": "1.2.3",
+ },
+ Object {
+ "version": "1-alpine",
+ },
+ Object {
+ "version": "2.0.0",
+ },
+ Object {
+ "version": "2-alpine",
+ },
+ ],
"sourceUrl": "https://github.com/renovatebot/renovate",
}
`;
@@ -820,7 +839,7 @@ Array [
"user-agent": "https://github.com/renovatebot/renovate",
},
"method": "GET",
- "url": "https://registry.company.com/v2/node/manifests/latest",
+ "url": "https://registry.company.com/v2/node/manifests/2-alpine",
},
Object {
"headers": Object {
@@ -895,7 +914,7 @@ Array [
"user-agent": "https://github.com/renovatebot/renovate",
},
"method": "GET",
- "url": "https://registry.company.com/v2/node/manifests/latest",
+ "url": "https://registry.company.com/v2/node/manifests/abc",
},
Object {
"headers": Object {
diff --git a/lib/datasource/docker/common.spec.ts b/lib/datasource/docker/common.spec.ts
index 84f07292090f1a..6c652b798a2846 100644
--- a/lib/datasource/docker/common.spec.ts
+++ b/lib/datasource/docker/common.spec.ts
@@ -10,7 +10,6 @@ jest.mock('../../util/host-rules');
describe(getName(), () => {
beforeEach(() => {
- httpMock.setup();
hostRules.find.mockReturnValue({
username: 'some-username',
password: 'some-password',
@@ -20,7 +19,6 @@ describe(getName(), () => {
afterEach(() => {
jest.resetAllMocks();
- httpMock.reset();
});
describe('getRegistryRepository', () => {
@@ -73,4 +71,48 @@ describe(getName(), () => {
`);
});
});
+ describe('getAuthHeaders', () => {
+ beforeEach(() => {
+ httpMock
+ .scope('https://my.local.registry')
+ .get('/v2/')
+ .reply(401, '', { 'www-authenticate': 'Authenticate you must' });
+ hostRules.hosts.mockReturnValue([]);
+ });
+
+ it('returns "authType token" if both provided', async () => {
+ hostRules.find.mockReturnValue({
+ authType: 'some-authType',
+ token: 'some-token',
+ });
+
+ const headers = await dockerCommon.getAuthHeaders(
+ 'https://my.local.registry',
+ 'https://my.local.registry/prefix'
+ );
+
+ expect(headers).toMatchInlineSnapshot(`
+ Object {
+ "authorization": "some-authType some-token",
+ }
+ `);
+ });
+
+ it('returns "Bearer token" if only token provided', async () => {
+ hostRules.find.mockReturnValue({
+ token: 'some-token',
+ });
+
+ const headers = await dockerCommon.getAuthHeaders(
+ 'https://my.local.registry',
+ 'https://my.local.registry/prefix'
+ );
+
+ expect(headers).toMatchInlineSnapshot(`
+ Object {
+ "authorization": "Bearer some-token",
+ }
+ `);
+ });
+ });
});
diff --git a/lib/datasource/docker/common.ts b/lib/datasource/docker/common.ts
index 14d9d1468e95e7..b90cf21ea0892a 100644
--- a/lib/datasource/docker/common.ts
+++ b/lib/datasource/docker/common.ts
@@ -81,12 +81,20 @@ export async function getAuthHeaders(
'base64'
);
opts.headers = { authorization: `Basic ${auth}` };
+ } else if (opts.token) {
+ const authType = opts.authType ?? 'Bearer';
+ logger.trace(
+ `Using ${authType} token for Docker registry ${registryHost}`
+ );
+ opts.headers = { authorization: `${authType} ${opts.token}` };
+ return opts.headers;
}
delete opts.username;
delete opts.password;
+ delete opts.token;
if (authenticateHeader.scheme.toUpperCase() === 'BASIC') {
- logger.debug(`Using Basic auth for docker registry ${dockerRepository}`);
+ logger.trace(`Using Basic auth for docker registry ${registryHost}`);
await http.get(apiCheckUrl, opts);
return opts.headers;
}
diff --git a/lib/datasource/docker/index.spec.ts b/lib/datasource/docker/index.spec.ts
index 2b82f076385743..7be603c7cb522e 100644
--- a/lib/datasource/docker/index.spec.ts
+++ b/lib/datasource/docker/index.spec.ts
@@ -44,7 +44,6 @@ function mockEcrAuthReject(msg: string) {
describe(getName(), () => {
beforeEach(() => {
- httpMock.setup();
hostRules.find.mockReturnValue({
username: 'some-username',
password: 'some-password',
@@ -54,7 +53,6 @@ describe(getName(), () => {
afterEach(() => {
jest.resetAllMocks();
- httpMock.reset();
});
describe('getDigest', () => {
@@ -625,8 +623,18 @@ describe(getName(), () => {
.times(3)
.reply(200)
.get('/node/tags/list?n=10000')
- .reply(200, { tags: ['latest'] })
- .get('/node/manifests/latest')
+ .reply(200, {
+ tags: [
+ '2.0.0',
+ '2-alpine',
+ '1-alpine',
+ '1.0.0',
+ '1.2.3',
+ '1.2.3-alpine',
+ 'abc',
+ ],
+ })
+ .get('/node/manifests/2-alpine')
.reply(200, {
schemaVersion: 2,
mediaType: MediaType.manifestV2,
@@ -657,8 +665,8 @@ describe(getName(), () => {
.times(4)
.reply(200)
.get('/node/tags/list?n=10000')
- .reply(200, { tags: ['latest'] })
- .get('/node/manifests/latest')
+ .reply(200, { tags: ['abc'] })
+ .get('/node/manifests/abc')
.reply(200, {
schemaVersion: 2,
mediaType: MediaType.manifestListV2,
diff --git a/lib/datasource/docker/index.ts b/lib/datasource/docker/index.ts
index b4aec34b47dfb0..843f058ece4802 100644
--- a/lib/datasource/docker/index.ts
+++ b/lib/datasource/docker/index.ts
@@ -3,7 +3,10 @@ import parseLinkHeader from 'parse-link-header';
import { logger } from '../../logger';
import { ExternalHostError } from '../../types/errors/external-host-error';
import * as packageCache from '../../util/cache/package';
-import * as dockerVersioning from '../../versioning/docker';
+import {
+ api as dockerVersioning,
+ id as dockerVersioningId,
+} from '../../versioning/docker';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import {
defaultRegistryUrls,
@@ -24,7 +27,7 @@ import { getTagsQuayRegistry } from './quay';
export { id };
export const customRegistrySupport = true;
export { defaultRegistryUrls };
-export const defaultVersioning = dockerVersioning.id;
+export const defaultVersioning = dockerVersioningId;
export const registryStrategy = 'first';
export const defaultConfig = {
@@ -142,6 +145,14 @@ async function getTags(
}
}
+function findLatestStable(tags: string[]): string {
+ const versions = tags
+ .filter((v) => dockerVersioning.isValid(v) && dockerVersioning.isStable(v))
+ .sort((a, b) => dockerVersioning.sortVersions(a, b));
+
+ return versions.pop() ?? tags.slice(-1).pop();
+}
+
/**
* docker.getDigest
*
@@ -228,7 +239,7 @@ export async function getReleases({
releases,
};
- const latestTag = tags.includes('latest') ? 'latest' : tags[tags.length - 1];
+ const latestTag = tags.includes('latest') ? 'latest' : findLatestStable(tags);
const labels = await getLabels(registryHost, dockerRepository, latestTag);
if (labels && 'org.opencontainers.image.source' in labels) {
ret.sourceUrl = labels['org.opencontainers.image.source'];
diff --git a/lib/datasource/docker/types.ts b/lib/datasource/docker/types.ts
index a5a2834a89daac..72f45fd37ec7fc 100644
--- a/lib/datasource/docker/types.ts
+++ b/lib/datasource/docker/types.ts
@@ -3,7 +3,7 @@
* https://docs.docker.com/registry/spec/manifest-v2-2/#media-types
*/
export enum MediaType {
- manifestV1 = 'pplication/vnd.docker.distribution.manifest.v1+json',
+ manifestV1 = 'application/vnd.docker.distribution.manifest.v1+json',
manifestV2 = 'application/vnd.docker.distribution.manifest.v2+json',
manifestListV2 = 'application/vnd.docker.distribution.manifest.list.v2+json',
}
diff --git a/lib/datasource/galaxy-collection/__snapshots__/index.spec.ts.snap b/lib/datasource/galaxy-collection/__snapshots__/index.spec.ts.snap
index cc3a0aac71f2f4..79bc53194c0673 100644
--- a/lib/datasource/galaxy-collection/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/galaxy-collection/__snapshots__/index.spec.ts.snap
@@ -113,23 +113,6 @@ exports[`datasource/galaxy-collection/index getReleases returns null for empty l
exports[`datasource/galaxy-collection/index getReleases returns null for null lookupName 1`] = `Array []`;
-exports[`datasource/galaxy-collection/index getReleases returns null for remote host error 1`] = `[Error: external-host-error]`;
-
-exports[`datasource/galaxy-collection/index getReleases returns null for remote host error 2`] = `
-Array [
- Object {
- "headers": Object {
- "accept": "application/json",
- "accept-encoding": "gzip, deflate, br",
- "host": "galaxy.ansible.com",
- "user-agent": "https://github.com/renovatebot/renovate",
- },
- "method": "GET",
- "url": "https://galaxy.ansible.com/api/v2/collections/foo/bar/",
- },
-]
-`;
-
exports[`datasource/galaxy-collection/index getReleases returns null for unexpected data at base 1`] = `
Array [
Object {
@@ -261,3 +244,43 @@ Array [
},
]
`;
+
+exports[`datasource/galaxy-collection/index getReleases throws error for remote host versions error 1`] = `
+Array [
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate, br",
+ "host": "galaxy.ansible.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ },
+ "method": "GET",
+ "url": "https://galaxy.ansible.com/api/v2/collections/community/kubernetes/",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate, br",
+ "host": "galaxy.ansible.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ },
+ "method": "GET",
+ "url": "https://galaxy.ansible.com/api/v2/collections/community/kubernetes/versions/",
+ },
+]
+`;
+
+exports[`datasource/galaxy-collection/index getReleases throws for remote host error 1`] = `
+Array [
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate, br",
+ "host": "galaxy.ansible.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ },
+ "method": "GET",
+ "url": "https://galaxy.ansible.com/api/v2/collections/foo/bar/",
+ },
+]
+`;
diff --git a/lib/datasource/galaxy-collection/index.spec.ts b/lib/datasource/galaxy-collection/index.spec.ts
index 8a5725cb1dbd80..9f1420a7cee9c1 100644
--- a/lib/datasource/galaxy-collection/index.spec.ts
+++ b/lib/datasource/galaxy-collection/index.spec.ts
@@ -1,8 +1,8 @@
import { getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { getName, loadFixture } from '../../../test/util';
-
-import { id as datasource } from '.';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
+import { GalaxyCollectionDatasource } from '.';
const communityKubernetesBase = loadFixture('community_kubernetes_base.json');
const communityKubernetesVersions = loadFixture(
@@ -20,34 +20,29 @@ const communityKubernetesDetails0111 = loadFixture(
const baseUrl = 'https://galaxy.ansible.com';
+const datasource = GalaxyCollectionDatasource.id;
+
describe(getName(), () => {
describe('getReleases', () => {
- beforeEach(() => {
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
- });
-
it('returns null for 404 result', async () => {
httpMock.scope(baseUrl).get('/api/v2/collections/foo/bar/').reply(404);
expect(
- await getPkgReleases({ datasource, depName: 'foo.bar' })
+ await getPkgReleases({
+ datasource,
+ depName: 'foo.bar',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
- it('returns null for remote host error', async () => {
+ it('throws for remote host error', async () => {
httpMock.scope(baseUrl).get('/api/v2/collections/foo/bar/').reply(500);
- let e;
- try {
- await getPkgReleases({ datasource, depName: 'foo.bar' });
- } catch (err) {
- e = err;
- }
- expect(e).toBeDefined();
- expect(e).toMatchSnapshot();
+ await expect(
+ getPkgReleases({
+ datasource,
+ depName: 'foo.bar',
+ })
+ ).rejects.toThrow(EXTERNAL_HOST_ERROR);
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -57,7 +52,10 @@ describe(getName(), () => {
.get('/api/v2/collections/community/kubernetes/')
.reply(200, '');
expect(
- await getPkgReleases({ datasource, depName: 'community.kubernetes' })
+ await getPkgReleases({
+ datasource,
+ depName: 'community.kubernetes',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -70,11 +68,30 @@ describe(getName(), () => {
.get('/api/v2/collections/community/kubernetes/versions/')
.reply(200, '');
expect(
- await getPkgReleases({ datasource, depName: 'community.kubernetes' })
+ await getPkgReleases({
+ datasource,
+ depName: 'community.kubernetes',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
+ it('throws error for remote host versions error', async () => {
+ httpMock
+ .scope(baseUrl)
+ .get('/api/v2/collections/community/kubernetes/')
+ .reply(200, communityKubernetesBase)
+ .get('/api/v2/collections/community/kubernetes/versions/')
+ .reply(500);
+ await expect(
+ getPkgReleases({
+ datasource,
+ depName: 'community.kubernetes',
+ })
+ ).rejects.toThrow(EXTERNAL_HOST_ERROR);
+ expect(httpMock.getTrace()).toMatchSnapshot();
+ });
+
it('returns only valid versions if a version detail fails', async () => {
httpMock
.scope(baseUrl)
@@ -101,12 +118,22 @@ describe(getName(), () => {
});
it('returns null for empty lookup', async () => {
- expect(await getPkgReleases({ datasource, depName: '' })).toBeNull();
+ expect(
+ await getPkgReleases({
+ datasource,
+ depName: '',
+ })
+ ).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
it('returns null for null lookupName ', async () => {
- expect(await getPkgReleases({ datasource, depName: null })).toBeNull();
+ expect(
+ await getPkgReleases({
+ datasource,
+ depName: null,
+ })
+ ).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -116,7 +143,10 @@ describe(getName(), () => {
.get('/api/v2/collections/foo/bar/')
.replyWithError('some unknown error');
expect(
- await getPkgReleases({ datasource, depName: 'foo.bar' })
+ await getPkgReleases({
+ datasource,
+ depName: 'foo.bar',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
diff --git a/lib/datasource/galaxy-collection/index.ts b/lib/datasource/galaxy-collection/index.ts
index df2f85e0ceaa8e..856c85561a08f8 100644
--- a/lib/datasource/galaxy-collection/index.ts
+++ b/lib/datasource/galaxy-collection/index.ts
@@ -1,8 +1,8 @@
import pMap from 'p-map';
import { logger } from '../../logger';
-import { ExternalHostError } from '../../types/errors/external-host-error';
-import * as packageCache from '../../util/cache/package';
-import { Http } from '../../util/http';
+import { cache } from '../../util/cache/package/decorator';
+import type { HttpResponse } from '../../util/http';
+import { Datasource } from '../datasource';
import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
import type {
BaseProjectResult,
@@ -10,33 +10,36 @@ import type {
VersionsProjectResult,
} from './types';
-export const id = 'galaxy-collection';
-export const defaultRegistryUrls = ['https://galaxy.ansible.com/'];
-export const customRegistrySupport = false;
+export class GalaxyCollectionDatasource extends Datasource {
+ static readonly id = 'galaxy-collection';
-const http = new Http(id);
-
-export async function getReleases({
- lookupName,
- registryUrl,
-}: GetReleasesConfig): Promise {
- const cacheNamespace = 'datasource-galaxy-collection';
- const cacheKey = lookupName;
- const cachedResult = await packageCache.get(
- cacheNamespace,
- cacheKey
- );
- // istanbul ignore if
- if (cachedResult) {
- return cachedResult;
+ constructor() {
+ super(GalaxyCollectionDatasource.id);
}
- const [namespace, projectName] = lookupName.split('.');
+ readonly customRegistrySupport = false;
+
+ readonly defaultRegistryUrls = ['https://galaxy.ansible.com/'];
+
+ @cache({
+ namespace: `datasource-${GalaxyCollectionDatasource.id}`,
+ key: ({ lookupName }: GetReleasesConfig) => lookupName,
+ })
+ async getReleases({
+ lookupName,
+ registryUrl,
+ }: GetReleasesConfig): Promise {
+ const [namespace, projectName] = lookupName.split('.');
- const baseUrl = `${registryUrl}api/v2/collections/${namespace}/${projectName}/`;
+ const baseUrl = `${registryUrl}api/v2/collections/${namespace}/${projectName}/`;
+
+ let baseUrlResponse: HttpResponse;
+ try {
+ baseUrlResponse = await this.http.getJson(baseUrl);
+ } catch (err) {
+ this.handleGenericErrors(err);
+ }
- try {
- const baseUrlResponse = await http.getJson(baseUrl);
if (!baseUrlResponse || !baseUrlResponse.body) {
logger.warn(
{ dependency: lookupName },
@@ -48,9 +51,16 @@ export async function getReleases({
const baseProject = baseUrlResponse.body;
const versionsUrl = `${baseUrl}versions/`;
- const versionsUrlResponse = await http.getJson(
- versionsUrl
- );
+
+ let versionsUrlResponse: HttpResponse;
+ try {
+ versionsUrlResponse = await this.http.getJson(
+ versionsUrl
+ );
+ } catch (err) {
+ this.handleGenericErrors(err);
+ }
+
const versionsProject = versionsUrlResponse.body;
const releases: Release[] = versionsProject.results.map((value) => {
@@ -66,7 +76,7 @@ export async function getReleases({
const enrichedReleases: Release[] = await pMap(
releases,
(basicRelease) =>
- http
+ this.http
.getJson(
`${versionsUrl}${basicRelease.version}/`
)
@@ -107,17 +117,6 @@ export async function getReleases({
homepage: newestVersionDetails?.metadata.homepage,
tags: newestVersionDetails?.metadata.tags,
};
-
- const cacheMinutes = 30;
- await packageCache.set(cacheNamespace, cacheKey, result, cacheMinutes);
return result;
- } catch (err) {
- if (
- err.statusCode === 429 ||
- (err.statusCode >= 500 && err.statusCode < 600)
- ) {
- throw new ExternalHostError(err);
- }
- throw err;
}
}
diff --git a/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap b/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap
index eccbcde4bb7fc3..2801cfba09c704 100644
--- a/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap
@@ -26,6 +26,7 @@ exports[`datasource/galaxy/index getReleases processes real data 2`] = `
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -40,6 +41,7 @@ exports[`datasource/galaxy/index getReleases return null if searching random use
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -54,6 +56,7 @@ exports[`datasource/galaxy/index getReleases returns null for 404 1`] = `
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -68,6 +71,7 @@ exports[`datasource/galaxy/index getReleases returns null for empty list 1`] = `
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -82,6 +86,7 @@ exports[`datasource/galaxy/index getReleases returns null for empty result 1`] =
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -96,6 +101,7 @@ exports[`datasource/galaxy/index getReleases returns null for missing fields 1`]
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -110,6 +116,7 @@ exports[`datasource/galaxy/index getReleases returns null for unknown error 1`]
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -126,6 +133,7 @@ exports[`datasource/galaxy/index getReleases throws for 5xx 2`] = `
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
@@ -140,6 +148,7 @@ exports[`datasource/galaxy/index getReleases throws for 404 1`] = `
Array [
Object {
"headers": Object {
+ "accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"host": "galaxy.ansible.com",
"user-agent": "https://github.com/renovatebot/renovate",
diff --git a/lib/datasource/galaxy/index.spec.ts b/lib/datasource/galaxy/index.spec.ts
index c4b16844a45f56..fd499327e029ef 100644
--- a/lib/datasource/galaxy/index.spec.ts
+++ b/lib/datasource/galaxy/index.spec.ts
@@ -1,8 +1,7 @@
import { getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { getName, loadFixture } from '../../../test/util';
-
-import { id as datasource } from '.';
+import { GalaxyDatasource } from '.';
const res1 = loadFixture('timezone');
const empty = loadFixture('empty');
@@ -11,21 +10,16 @@ const baseUrl = 'https://galaxy.ansible.com/';
describe(getName(), () => {
describe('getReleases', () => {
- beforeEach(() => {
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
- });
-
it('returns null for empty result', async () => {
httpMock
.scope(baseUrl)
.get('/api/v1/roles/?owner__username=non_existent_crate&name=undefined')
.reply(200);
expect(
- await getPkgReleases({ datasource, depName: 'non_existent_crate' })
+ await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'non_existent_crate',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -35,7 +29,10 @@ describe(getName(), () => {
.get('/api/v1/roles/?owner__username=non_existent_crate&name=undefined')
.reply(200, undefined);
expect(
- await getPkgReleases({ datasource, depName: 'non_existent_crate' })
+ await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'non_existent_crate',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -45,7 +42,10 @@ describe(getName(), () => {
.get('/api/v1/roles/?owner__username=non_existent_crate&name=undefined')
.reply(200, '\n');
expect(
- await getPkgReleases({ datasource, depName: 'non_existent_crate' })
+ await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'non_existent_crate',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -55,7 +55,10 @@ describe(getName(), () => {
.get('/api/v1/roles/?owner__username=some_crate&name=undefined')
.reply(404);
expect(
- await getPkgReleases({ datasource, depName: 'some_crate' })
+ await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'some_crate',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -65,7 +68,10 @@ describe(getName(), () => {
.get('/api/v1/roles/?owner__username=some_crate&name=undefined')
.replyWithError('some unknown error');
expect(
- await getPkgReleases({ datasource, depName: 'some_crate' })
+ await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'some_crate',
+ })
).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -75,7 +81,7 @@ describe(getName(), () => {
.get('/api/v1/roles/?owner__username=yatesr&name=timezone')
.reply(200, res1);
const res = await getPkgReleases({
- datasource,
+ datasource: GalaxyDatasource.id,
depName: 'yatesr.timezone',
});
expect(res).toMatchSnapshot();
@@ -88,7 +94,10 @@ describe(getName(), () => {
.scope(baseUrl)
.get('/api/v1/roles/?owner__username=foo&name=bar')
.reply(200, empty);
- const res = await getPkgReleases({ datasource, depName: 'foo.bar' });
+ const res = await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'foo.bar',
+ });
expect(res).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
@@ -99,7 +108,10 @@ describe(getName(), () => {
.reply(502);
let e;
try {
- await getPkgReleases({ datasource, depName: 'some_crate' });
+ await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'some_crate',
+ });
} catch (err) {
e = err;
}
@@ -112,7 +124,10 @@ describe(getName(), () => {
.scope(baseUrl)
.get('/api/v1/roles/?owner__username=foo&name=bar')
.reply(404);
- const res = await getPkgReleases({ datasource, depName: 'foo.bar' });
+ const res = await getPkgReleases({
+ datasource: GalaxyDatasource.id,
+ depName: 'foo.bar',
+ });
expect(res).toBeNull();
expect(httpMock.getTrace()).toMatchSnapshot();
});
diff --git a/lib/datasource/galaxy/index.ts b/lib/datasource/galaxy/index.ts
index 87e41b9346684b..ad9c03595e7b84 100644
--- a/lib/datasource/galaxy/index.ts
+++ b/lib/datasource/galaxy/index.ts
@@ -1,44 +1,51 @@
import { logger } from '../../logger';
-import { ExternalHostError } from '../../types/errors/external-host-error';
-import * as packageCache from '../../util/cache/package';
-import { Http } from '../../util/http';
+import { cache } from '../../util/cache/package/decorator';
+import { HttpResponse } from '../../util/http';
+import { Datasource } from '../datasource';
import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
+import type { GalaxyResult } from './types';
-export const id = 'galaxy';
-export const defaultRegistryUrls = ['https://galaxy.ansible.com/'];
-export const customRegistrySupport = false;
-
-const http = new Http(id);
-
-export async function getReleases({
- lookupName,
- registryUrl,
-}: GetReleasesConfig): Promise {
- const cacheNamespace = 'datasource-galaxy';
- const cacheKey = lookupName;
- const cachedResult = await packageCache.get(
- cacheNamespace,
- cacheKey
- );
- // istanbul ignore if
- if (cachedResult) {
- return cachedResult;
+export class GalaxyDatasource extends Datasource {
+ static readonly id = 'galaxy';
+
+ constructor() {
+ super(GalaxyDatasource.id);
}
- const lookUp = lookupName.split('.');
- const userName = lookUp[0];
- const projectName = lookUp[1];
-
- const galaxyAPIUrl =
- registryUrl +
- 'api/v1/roles/?owner__username=' +
- userName +
- '&name=' +
- projectName;
- const galaxyProjectUrl = registryUrl + userName + '/' + projectName;
- try {
- let res: any = await http.get(galaxyAPIUrl);
- if (!res || !res.body) {
+ readonly customRegistrySupport = false;
+
+ readonly defaultRegistryUrls = ['https://galaxy.ansible.com/'];
+
+ @cache({
+ namespace: 'datasource-galaxy',
+ key: (getReleasesConfig: GetReleasesConfig) => getReleasesConfig.lookupName,
+ })
+ async getReleases({
+ lookupName,
+ registryUrl,
+ }: GetReleasesConfig): Promise {
+ const lookUp = lookupName.split('.');
+ const userName = lookUp[0];
+ const projectName = lookUp[1];
+
+ const galaxyAPIUrl =
+ registryUrl +
+ 'api/v1/roles/?owner__username=' +
+ userName +
+ '&name=' +
+ projectName;
+ const galaxyProjectUrl = registryUrl + userName + '/' + projectName;
+
+ let raw: HttpResponse = null;
+ try {
+ raw = await this.http.getJson(galaxyAPIUrl);
+ } catch (err) {
+ this.handleGenericErrors(err);
+ }
+
+ const body = raw?.body;
+
+ if (!body) {
logger.warn(
{ dependency: lookupName },
`Received invalid data from ${galaxyAPIUrl}`
@@ -46,18 +53,15 @@ export async function getReleases({
return null;
}
- res = res.body;
- const response = JSON.parse(res);
-
// istanbul ignore if
- if (response.results.length > 1) {
+ if (body.results.length > 1) {
logger.warn(
{ dependency: lookupName },
`Received multiple results from ${galaxyAPIUrl}`
);
return null;
}
- if (response.results.length === 0) {
+ if (body.results.length === 0) {
logger.info(
{ dependency: lookupName },
`Received no results from ${galaxyAPIUrl}`
@@ -65,7 +69,7 @@ export async function getReleases({
return null;
}
- const resultObject = response.results[0];
+ const resultObject = body.results[0];
const versions = resultObject.summary_fields.versions;
const result: ReleaseResult = {
@@ -88,16 +92,7 @@ export async function getReleases({
return release;
}
);
- const cacheMinutes = 10;
- await packageCache.set(cacheNamespace, cacheKey, result, cacheMinutes);
+
return result;
- } catch (err) {
- if (
- err.statusCode === 429 ||
- (err.statusCode >= 500 && err.statusCode < 600)
- ) {
- throw new ExternalHostError(err);
- }
- throw err;
}
}
diff --git a/lib/datasource/galaxy/types.ts b/lib/datasource/galaxy/types.ts
new file mode 100644
index 00000000000000..5b3dbf9918171e
--- /dev/null
+++ b/lib/datasource/galaxy/types.ts
@@ -0,0 +1,12 @@
+export interface GalaxyResult {
+ results: {
+ summary_fields: {
+ versions: {
+ name: string;
+ release_date: string;
+ }[];
+ };
+ github_user: string;
+ github_repo: string;
+ }[];
+}
diff --git a/lib/datasource/github-releases/index.spec.ts b/lib/datasource/github-releases/index.spec.ts
index 4bf1e18110c932..35ff90fbc92cd4 100644
--- a/lib/datasource/github-releases/index.spec.ts
+++ b/lib/datasource/github-releases/index.spec.ts
@@ -29,11 +29,6 @@ describe(getName(), () => {
hostRules.find.mockReturnValue({
token: 'some-token',
});
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
describe('getReleases', () => {
diff --git a/lib/datasource/github-tags/index.spec.ts b/lib/datasource/github-tags/index.spec.ts
index cd038f8f6c51bf..26ad50fa7ad22b 100644
--- a/lib/datasource/github-tags/index.spec.ts
+++ b/lib/datasource/github-tags/index.spec.ts
@@ -12,17 +12,12 @@ const githubEnterpriseApiHost = 'https://git.enterprise.com';
describe(getName(), () => {
beforeEach(() => {
- httpMock.reset();
- httpMock.setup();
jest.resetAllMocks();
hostRules.hosts = jest.fn(() => []);
hostRules.find.mockReturnValue({
token: 'some-token',
});
});
- afterEach(() => {
- httpMock.reset();
- });
describe('getDigest', () => {
const lookupName = 'some/dep';
@@ -110,17 +105,12 @@ describe(getName(), () => {
});
describe('getReleases', () => {
beforeEach(() => {
- httpMock.reset();
- httpMock.setup();
jest.resetAllMocks();
hostRules.hosts = jest.fn(() => []);
hostRules.find.mockReturnValue({
token: 'some-token',
});
});
- afterEach(() => {
- httpMock.reset();
- });
const depName = 'some/dep2';
diff --git a/lib/datasource/gitlab-tags/index.spec.ts b/lib/datasource/gitlab-tags/index.spec.ts
index 1b106f37faa734..c451a6a8b8abdb 100644
--- a/lib/datasource/gitlab-tags/index.spec.ts
+++ b/lib/datasource/gitlab-tags/index.spec.ts
@@ -4,13 +4,6 @@ import { getName } from '../../../test/util';
import { id as datasource } from '.';
describe(getName(), () => {
- beforeEach(() => {
- httpMock.reset();
- httpMock.setup();
- });
- afterEach(() => {
- httpMock.reset();
- });
describe('getReleases', () => {
it('returns tags from custom registry', async () => {
const body = [
diff --git a/lib/datasource/go/index.spec.ts b/lib/datasource/go/index.spec.ts
index 0be03b9737deb2..d5c4ea059a00a6 100644
--- a/lib/datasource/go/index.spec.ts
+++ b/lib/datasource/go/index.spec.ts
@@ -48,13 +48,11 @@ const resGitHubEnterprise = `
describe(getName(), () => {
beforeEach(() => {
- httpMock.setup();
hostRules.find.mockReturnValue({});
hostRules.hosts.mockReturnValue([]);
});
afterEach(() => {
- httpMock.reset();
jest.resetAllMocks();
});
@@ -374,7 +372,6 @@ describe(getName(), () => {
const tags = [{ name: 'a/v1.0.0' }, { name: 'b/v2.0.0' }];
for (const pkg of packages) {
- httpMock.setup();
httpMock
.scope('https://api.github.com/')
.get('/repos/x/text/tags?per_page=100')
@@ -389,7 +386,7 @@ describe(getName(), () => {
const httpCalls = httpMock.getTrace();
expect(httpCalls).toMatchSnapshot();
- httpMock.reset();
+ httpMock.clear();
}
});
it('returns none if no tags match submodules', async () => {
@@ -400,7 +397,6 @@ describe(getName(), () => {
const tags = [{ name: 'v1.0.0' }, { name: 'v2.0.0' }];
for (const pkg of packages) {
- httpMock.setup();
httpMock
.scope('https://api.github.com/')
.get('/repos/x/text/tags?per_page=100')
@@ -413,7 +409,7 @@ describe(getName(), () => {
const httpCalls = httpMock.getTrace();
expect(httpCalls).toMatchSnapshot();
- httpMock.reset();
+ httpMock.clear();
}
});
it('works for nested modules on github v2+ major upgrades', async () => {
diff --git a/lib/datasource/gradle-version/index.spec.ts b/lib/datasource/gradle-version/index.spec.ts
index 078303f6e647b1..4da63d73df8b05 100644
--- a/lib/datasource/gradle-version/index.spec.ts
+++ b/lib/datasource/gradle-version/index.spec.ts
@@ -3,12 +3,14 @@ import * as httpMock from '../../../test/http-mock';
import { getName, loadJsonFixture, partial } from '../../../test/util';
import { ExternalHostError } from '../../types/errors/external-host-error';
import { id as versioning } from '../../versioning/gradle';
-import { id as datasource, getReleases } from '.';
+import { GradleVersionDatasource } from '.';
const allResponse: any = loadJsonFixture('all.json');
let config: GetPkgReleasesConfig;
+const datasource = GradleVersionDatasource.id;
+
describe(getName(), () => {
describe('getReleases', () => {
beforeEach(() => {
@@ -18,11 +20,6 @@ describe(getName(), () => {
depName: 'abc',
};
jest.clearAllMocks();
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
it('processes real data', async () => {
@@ -76,12 +73,14 @@ describe(getName(), () => {
httpMock
.scope('https://services.gradle.org/')
.get('/versions/all')
- .reply(404);
+ .reply(500);
- httpMock.scope('http://baz.qux').get('/').reply(404);
+ httpMock.scope('http://baz.qux').get('/').reply(429);
+
+ const gradleVersionDatasource = new GradleVersionDatasource();
await expect(
- getReleases(
+ gradleVersionDatasource.getReleases(
partial({
registryUrl: 'https://services.gradle.org/versions/all',
})
@@ -89,12 +88,12 @@ describe(getName(), () => {
).rejects.toThrow(ExternalHostError);
await expect(
- getReleases(
+ gradleVersionDatasource.getReleases(
partial({
registryUrl: 'http://baz.qux',
})
)
- ).rejects.toThrow('Response code 404 (Not Found)');
+ ).rejects.toThrow(ExternalHostError);
expect(httpMock.getTrace()).toMatchSnapshot();
});
});
diff --git a/lib/datasource/gradle-version/index.ts b/lib/datasource/gradle-version/index.ts
index 6638ea55b5f01f..56756a7e092b35 100644
--- a/lib/datasource/gradle-version/index.ts
+++ b/lib/datasource/gradle-version/index.ts
@@ -1,63 +1,71 @@
-import { ExternalHostError } from '../../types/errors/external-host-error';
-import { Http } from '../../util/http';
-import { HttpError } from '../../util/http/types';
+import { cache } from '../../util/cache/package/decorator';
import { regEx } from '../../util/regex';
import * as gradleVersioning from '../../versioning/gradle';
+import { Datasource } from '../datasource';
import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
import type { GradleRelease } from './types';
-export const id = 'gradle-version';
-export const customRegistrySupport = true;
-export const defaultRegistryUrls = ['https://services.gradle.org/versions/all'];
-export const defaultVersioning = gradleVersioning.id;
-export const registryStrategy = 'merge';
+export class GradleVersionDatasource extends Datasource {
+ static readonly id = 'gradle-version';
-const http = new Http(id);
+ constructor() {
+ super(GradleVersionDatasource.id);
+ }
-const buildTimeRegex = regEx(
- '^(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\+\\d\\d\\d\\d)$'
-);
+ readonly defaultRegistryUrls = ['https://services.gradle.org/versions/all'];
-function formatBuildTime(timeStr: string): string | null {
- if (!timeStr) {
- return null;
- }
- if (buildTimeRegex.test(timeStr)) {
- return timeStr.replace(buildTimeRegex, '$1-$2-$3T$4:$5:$6$7');
- }
- return null;
-}
+ readonly defaultVersioning = gradleVersioning.id;
+
+ readonly registryStrategy = 'merge';
+
+ private static readonly buildTimeRegex = regEx(
+ '^(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\+\\d\\d\\d\\d)$'
+ );
-export async function getReleases({
- registryUrl,
-}: GetReleasesConfig): Promise {
- let releases: Release[];
- try {
- const response = await http.getJson(registryUrl);
- releases = response.body
- .filter((release) => !release.snapshot && !release.nightly)
- .map((release) => ({
- version: release.version,
- releaseTimestamp: formatBuildTime(release.buildTime),
- ...(release.broken && { isDeprecated: release.broken }),
- }));
- } catch (err) {
- if (
- err instanceof HttpError &&
- err.response?.url === defaultRegistryUrls[0]
- ) {
- throw new ExternalHostError(err);
+ @cache({
+ namespace: `datasource-${GradleVersionDatasource.id}`,
+ key: ({ registryUrl }: GetReleasesConfig) => registryUrl,
+ })
+ async getReleases({
+ registryUrl,
+ }: GetReleasesConfig): Promise {
+ let releases: Release[];
+ try {
+ const response = await this.http.getJson(registryUrl);
+ releases = response.body
+ .filter((release) => !release.snapshot && !release.nightly)
+ .map((release) => ({
+ version: release.version,
+ releaseTimestamp: GradleVersionDatasource.formatBuildTime(
+ release.buildTime
+ ),
+ ...(release.broken && { isDeprecated: release.broken }),
+ }));
+ } catch (err) {
+ this.handleGenericErrors(err);
}
- throw err;
+
+ const res: ReleaseResult = {
+ releases,
+ homepage: 'https://gradle.org',
+ sourceUrl: 'https://github.com/gradle/gradle',
+ };
+ if (res.releases.length) {
+ return res;
+ }
+ return null;
}
- const res: ReleaseResult = {
- releases,
- homepage: 'https://gradle.org',
- sourceUrl: 'https://github.com/gradle/gradle',
- };
- if (res.releases.length) {
- return res;
+ private static formatBuildTime(timeStr: string): string | null {
+ if (!timeStr) {
+ return null;
+ }
+ if (GradleVersionDatasource.buildTimeRegex.test(timeStr)) {
+ return timeStr.replace(
+ GradleVersionDatasource.buildTimeRegex,
+ '$1-$2-$3T$4:$5:$6$7'
+ );
+ }
+ return null;
}
- return null;
}
diff --git a/lib/datasource/helm/index.spec.ts b/lib/datasource/helm/index.spec.ts
index ff3a14ed97537f..83ce27950c9394 100644
--- a/lib/datasource/helm/index.spec.ts
+++ b/lib/datasource/helm/index.spec.ts
@@ -1,7 +1,7 @@
import { getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { getName, loadFixture } from '../../../test/util';
-import { id as datasource } from '.';
+import { HelmDatasource } from '.';
// Truncated index.yaml file
const indexYaml = loadFixture('index.yaml');
@@ -10,17 +10,12 @@ describe(getName(), () => {
describe('getReleases', () => {
beforeEach(() => {
jest.resetAllMocks();
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
it('returns null if lookupName was not provided', async () => {
expect(
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: undefined,
registryUrls: ['https://example-repository.com'],
})
@@ -29,7 +24,7 @@ describe(getName(), () => {
it('returns null if repository was not provided', async () => {
expect(
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'some_chart',
registryUrls: [],
})
@@ -42,7 +37,7 @@ describe(getName(), () => {
.reply(200, null);
expect(
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'non_existent_chart',
registryUrls: ['https://example-repository.com'],
})
@@ -56,7 +51,7 @@ describe(getName(), () => {
.reply(200, undefined);
expect(
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'non_existent_chart',
registryUrls: ['https://example-repository.com'],
})
@@ -70,7 +65,7 @@ describe(getName(), () => {
.reply(404);
expect(
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'some_chart',
registryUrls: ['https://example-repository.com'],
})
@@ -85,7 +80,7 @@ describe(getName(), () => {
let e;
try {
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'some_chart',
registryUrls: ['https://example-repository.com'],
});
@@ -103,7 +98,7 @@ describe(getName(), () => {
.replyWithError('');
expect(
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'some_chart',
registryUrls: ['https://example-repository.com'],
})
@@ -116,7 +111,7 @@ describe(getName(), () => {
.get('/index.yaml')
.reply(200, '# A comment');
const releases = await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'non_existent_chart',
registryUrls: ['https://example-repository.com'],
});
@@ -135,7 +130,7 @@ describe(getName(), () => {
.get('/index.yaml')
.reply(200, res);
const releases = await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'non_existent_chart',
registryUrls: ['https://example-repository.com'],
});
@@ -148,7 +143,7 @@ describe(getName(), () => {
.get('/index.yaml')
.reply(200, indexYaml);
const releases = await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'non_existent_chart',
registryUrls: ['https://example-repository.com'],
});
@@ -161,7 +156,7 @@ describe(getName(), () => {
.get('/index.yaml')
.reply(200, indexYaml);
const releases = await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'ambassador',
registryUrls: ['https://example-repository.com'],
});
@@ -175,7 +170,7 @@ describe(getName(), () => {
.get('/subdir/index.yaml')
.reply(200, indexYaml);
await getPkgReleases({
- datasource,
+ datasource: HelmDatasource.id,
depName: 'ambassador',
registryUrls: ['https://example-repository.com/subdir'],
});
diff --git a/lib/datasource/helm/index.ts b/lib/datasource/helm/index.ts
index 3090fe30131aa6..6cf0e412cbab43 100644
--- a/lib/datasource/helm/index.ts
+++ b/lib/datasource/helm/index.ts
@@ -1,104 +1,90 @@
import is from '@sindresorhus/is';
import { load } from 'js-yaml';
import { logger } from '../../logger';
-import { ExternalHostError } from '../../types/errors/external-host-error';
-import * as packageCache from '../../util/cache/package';
-import { Http } from '../../util/http';
+import { cache } from '../../util/cache/package/decorator';
import { ensureTrailingSlash } from '../../util/url';
+import { Datasource } from '../datasource';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import type { HelmRepository, RepositoryData } from './types';
-export const id = 'helm';
+export class HelmDatasource extends Datasource {
+ static readonly id = 'helm';
-const http = new Http(id);
+ constructor() {
+ super(HelmDatasource.id);
+ }
-export const customRegistrySupport = true;
-export const defaultRegistryUrls = ['https://charts.helm.sh/stable'];
-export const registryStrategy = 'first';
+ readonly defaultRegistryUrls = ['https://charts.helm.sh/stable'];
-export const defaultConfig = {
- commitMessageTopic: 'Helm release {{depName}}',
- group: {
- commitMessageTopic: '{{{groupName}}} Helm releases',
- },
-};
+ readonly defaultConfig = {
+ commitMessageTopic: 'Helm release {{depName}}',
+ group: {
+ commitMessageTopic: '{{{groupName}}} Helm releases',
+ },
+ };
-export async function getRepositoryData(
- repository: string
-): Promise {
- const cacheNamespace = 'datasource-helm';
- const cacheKey = repository;
- const cachedIndex = await packageCache.get(
- cacheNamespace,
- cacheKey
- );
- // istanbul ignore if
- if (cachedIndex) {
- return cachedIndex;
- }
- let res: any;
- try {
- res = await http.get('index.yaml', {
- baseUrl: ensureTrailingSlash(repository),
- });
- if (!res || !res.body) {
- logger.warn(`Received invalid response from ${repository}`);
- return null;
- }
- } catch (err) {
- if (
- err.statusCode === 429 ||
- (err.statusCode >= 500 && err.statusCode < 600)
- ) {
- throw new ExternalHostError(err);
+ @cache({
+ namespace: `datasource-${HelmDatasource.id}`,
+ key: (repository: string) => repository,
+ })
+ async getRepositoryData(repository: string): Promise {
+ let res: any;
+ try {
+ res = await this.http.get('index.yaml', {
+ baseUrl: ensureTrailingSlash(repository),
+ });
+ if (!res || !res.body) {
+ logger.warn(`Received invalid response from ${repository}`);
+ return null;
+ }
+ } catch (err) {
+ this.handleGenericErrors(err);
}
- throw err;
- }
- try {
- const doc = load(res.body, {
- json: true,
- }) as HelmRepository;
- if (!is.plainObject(doc)) {
+ try {
+ const doc = load(res.body, {
+ json: true,
+ }) as HelmRepository;
+ if (!is.plainObject(doc)) {
+ logger.warn(`Failed to parse index.yaml from ${repository}`);
+ return null;
+ }
+ const result: RepositoryData = {};
+ for (const [name, releases] of Object.entries(doc.entries)) {
+ result[name] = {
+ homepage: releases[0].home,
+ sourceUrl: releases[0].sources ? releases[0].sources[0] : undefined,
+ releases: releases.map((release) => ({
+ version: release.version,
+ releaseTimestamp: release.created ? release.created : null,
+ })),
+ };
+ }
+
+ return result;
+ } catch (err) {
logger.warn(`Failed to parse index.yaml from ${repository}`);
+ logger.debug(err);
return null;
}
- const result: RepositoryData = {};
- for (const [name, releases] of Object.entries(doc.entries)) {
- result[name] = {
- homepage: releases[0].home,
- sourceUrl: releases[0].sources ? releases[0].sources[0] : undefined,
- releases: releases.map((release) => ({
- version: release.version,
- releaseTimestamp: release.created ? release.created : null,
- })),
- };
- }
- const cacheMinutes = 20;
- await packageCache.set(cacheNamespace, cacheKey, result, cacheMinutes);
- return result;
- } catch (err) {
- logger.warn(`Failed to parse index.yaml from ${repository}`);
- logger.debug(err);
- return null;
}
-}
-export async function getReleases({
- lookupName,
- registryUrl: helmRepository,
-}: GetReleasesConfig): Promise {
- const repositoryData = await getRepositoryData(helmRepository);
- if (!repositoryData) {
- logger.debug(`Couldn't get index.yaml file from ${helmRepository}`);
- return null;
- }
- const releases = repositoryData[lookupName];
- if (!releases) {
- logger.debug(
- { dependency: lookupName },
- `Entry ${lookupName} doesn't exist in index.yaml from ${helmRepository}`
- );
- return null;
+ async getReleases({
+ lookupName,
+ registryUrl: helmRepository,
+ }: GetReleasesConfig): Promise {
+ const repositoryData = await this.getRepositoryData(helmRepository);
+ if (!repositoryData) {
+ logger.debug(`Couldn't get index.yaml file from ${helmRepository}`);
+ return null;
+ }
+ const releases = repositoryData[lookupName];
+ if (!releases) {
+ logger.debug(
+ { dependency: lookupName },
+ `Entry ${lookupName} doesn't exist in index.yaml from ${helmRepository}`
+ );
+ return null;
+ }
+ return releases;
}
- return releases;
}
diff --git a/lib/datasource/hex/index.spec.ts b/lib/datasource/hex/index.spec.ts
index 739be18d1e017a..d6f453018a816a 100644
--- a/lib/datasource/hex/index.spec.ts
+++ b/lib/datasource/hex/index.spec.ts
@@ -17,12 +17,10 @@ describe(getName(), () => {
beforeEach(() => {
hostRules.hosts.mockReturnValue([]);
hostRules.find.mockReturnValue({});
- httpMock.setup();
});
afterEach(() => {
jest.resetAllMocks();
- httpMock.reset();
});
describe('getReleases', () => {
diff --git a/lib/datasource/index.spec.ts b/lib/datasource/index.spec.ts
index f244fe575c668b..e16a860a733d76 100644
--- a/lib/datasource/index.spec.ts
+++ b/lib/datasource/index.spec.ts
@@ -7,7 +7,7 @@ import { ExternalHostError } from '../types/errors/external-host-error';
import { loadModules } from '../util/modules';
import { Datasource } from './datasource';
import * as datasourceDocker from './docker';
-import * as datasourceGalaxy from './galaxy';
+import { GalaxyDatasource } from './galaxy';
import * as datasourceGithubTags from './github-tags';
import * as datasourceMaven from './maven';
import * as datasourceNpm from './npm';
@@ -16,13 +16,11 @@ import type { DatasourceApi } from './types';
import * as datasource from '.';
jest.mock('./docker');
-jest.mock('./galaxy');
jest.mock('./maven');
jest.mock('./npm');
jest.mock('./packagist');
const dockerDatasource = mocked(datasourceDocker);
-const galaxyDatasource = mocked(datasourceGalaxy);
const mavenDatasource = mocked(datasourceMaven);
const npmDatasource = mocked(datasourceNpm);
const packagistDatasource = mocked(datasourcePackagist);
@@ -151,9 +149,8 @@ describe(getName(), () => {
expect(res.sourceUrl).toBeDefined();
});
it('ignores and warns for registryUrls', async () => {
- galaxyDatasource.getReleases.mockResolvedValue(null);
await datasource.getPkgReleases({
- datasource: datasourceGalaxy.id,
+ datasource: GalaxyDatasource.id,
depName: 'some.dep',
registryUrls: ['https://google.com/'],
});
diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts
index 09d3488deddc68..be184fbb3dd81a 100644
--- a/lib/datasource/index.ts
+++ b/lib/datasource/index.ts
@@ -188,7 +188,7 @@ function resolveRegistryUrls(
if (is.nonEmptyArray(extractedUrls)) {
logger.warn(
{ datasource: datasource.id, registryUrls: extractedUrls },
- 'Custom datasources are not allowed for this datasource and will be ignored'
+ 'Custom registries are not allowed for this datasource and will be ignored'
);
}
return defaultRegistryUrls;
diff --git a/lib/datasource/jenkins-plugins/index.spec.ts b/lib/datasource/jenkins-plugins/index.spec.ts
index 0b7fa723353bad..8a2e7eb9a69c54 100644
--- a/lib/datasource/jenkins-plugins/index.spec.ts
+++ b/lib/datasource/jenkins-plugins/index.spec.ts
@@ -21,7 +21,6 @@ describe(getName(), () => {
beforeEach(() => {
resetCache();
- httpMock.setup();
process.env.RENOVATE_SKIP_CACHE = 'true';
jest.resetAllMocks();
});
@@ -30,7 +29,6 @@ describe(getName(), () => {
if (!httpMock.allUsed()) {
throw new Error('Not all http mocks have been used!');
}
- httpMock.reset();
process.env.RENOVATE_SKIP_CACHE = SKIP_CACHE;
});
diff --git a/lib/datasource/maven/__snapshots__/index.spec.ts.snap b/lib/datasource/maven/__snapshots__/index.spec.ts.snap
index 0ad97a19158aba..f401e28a77d53d 100644
--- a/lib/datasource/maven/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/maven/__snapshots__/index.spec.ts.snap
@@ -1170,43 +1170,6 @@ Array [
]
`;
-exports[`datasource/maven/index supports file protocol 1`] = `
-Object {
- "display": "org.example:package",
- "group": "org.example",
- "homepage": "https://package.example.org/about",
- "name": "package",
- "registryUrl": "file:///bar",
- "releases": Array [
- Object {
- "version": "1.0.0",
- },
- Object {
- "version": "1.0.1",
- },
- Object {
- "version": "1.0.2",
- },
- Object {
- "version": "2.0.0",
- },
- ],
-}
-`;
-
-exports[`datasource/maven/index supports file protocol 2`] = `
-Array [
- Array [
- "/bar/org/example/package/maven-metadata.xml",
- "utf8",
- ],
- Array [
- "/bar/org/example/package/2.0.0/package-2.0.0.pom",
- "utf8",
- ],
-]
-`;
-
exports[`datasource/maven/index throws EXTERNAL_HOST_ERROR for 50x 1`] = `
Array [
Object {
diff --git a/lib/datasource/maven/index.spec.ts b/lib/datasource/maven/index.spec.ts
index 6324f9a1e28e42..3c48e4c8a8632f 100644
--- a/lib/datasource/maven/index.spec.ts
+++ b/lib/datasource/maven/index.spec.ts
@@ -1,15 +1,10 @@
-import _fs from 'fs-extra';
import { ReleaseResult, getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
-import { getName, loadFixture, mocked } from '../../../test/util';
import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
import * as hostRules from '../../util/host-rules';
import { id as versioning } from '../../versioning/maven';
import { id as datasource } from '.';
-jest.mock('fs-extra');
-const fs = mocked(_fs);
-
const baseUrl = 'https://repo.maven.apache.org/maven2';
const baseUrlCustom = 'https://custom.registry.renovatebot.com';
@@ -86,12 +81,10 @@ describe(getName(), () => {
token: 'abc123',
});
jest.resetAllMocks();
- httpMock.setup();
});
afterEach(() => {
hostRules.clear();
- httpMock.reset();
delete process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK;
});
@@ -312,21 +305,6 @@ describe(getName(), () => {
expect(httpMock.getTrace()).toMatchSnapshot();
});
- it('supports file protocol', async () => {
- fs.exists.mockResolvedValueOnce(false);
-
- fs.exists.mockResolvedValueOnce(true);
- fs.readFile.mockResolvedValueOnce(Buffer.from(loadFixture('metadata.xml')));
-
- fs.exists.mockResolvedValueOnce(true);
- fs.readFile.mockResolvedValueOnce(Buffer.from(loadFixture('pom.xml')));
-
- const res = await get('org.example:package', 'file:///foo', 'file:///bar');
-
- expect(res).toMatchSnapshot();
- expect(fs.readFile.mock.calls).toMatchSnapshot();
- });
-
describe('fetching parent info', () => {
const parentPackage = {
dep: 'org.example:parent',
diff --git a/lib/datasource/maven/index.ts b/lib/datasource/maven/index.ts
index 8b368ed56bb6bd..20e6aef5764e94 100644
--- a/lib/datasource/maven/index.ts
+++ b/lib/datasource/maven/index.ts
@@ -95,18 +95,6 @@ function isValidArtifactsInfo(
return versions.every((v) => info[v] !== undefined);
}
-async function getArtifactInfo(
- version: string,
- artifactUrl: url.URL
-): Promise {
- const proto = artifactUrl.protocol;
- if (proto === 'http:' || proto === 'https:') {
- const result = await isHttpResourceExists(artifactUrl);
- return [version, result];
- }
- return [version, true];
-}
-
async function filterMissingArtifacts(
dependency: MavenDependency,
repoUrl: string,
@@ -130,8 +118,8 @@ async function filterMissingArtifacts(
.filter(([_, artifactUrl]) => Boolean(artifactUrl))
.map(
([version, artifactUrl]) =>
- (): Promise =>
- getArtifactInfo(version, artifactUrl)
+ async (): Promise =>
+ [version, await isHttpResourceExists(artifactUrl)]
);
const results = await pAll(queue, { concurrency: 5 });
artifactsInfo = results.reduce(
diff --git a/lib/datasource/maven/util.ts b/lib/datasource/maven/util.ts
index 2e8c96c8c4144a..18316d73df55b2 100644
--- a/lib/datasource/maven/util.ts
+++ b/lib/datasource/maven/util.ts
@@ -1,5 +1,4 @@
import url from 'url';
-import fs from 'fs-extra';
import { XmlDocument } from 'xmldoc';
import { HOST_DISABLED } from '../../constants/error-messages';
import { logger } from '../../logger';
@@ -100,14 +99,6 @@ export async function downloadHttpProtocol(
}
}
-async function downloadFileProtocol(pkgUrl: url.URL): Promise {
- const pkgPath = pkgUrl.toString().replace('file://', '');
- if (!(await fs.exists(pkgPath))) {
- return null;
- }
- return fs.readFile(pkgPath, 'utf8');
-}
-
export async function isHttpResourceExists(
pkgUrl: url.URL | string,
hostType = id
@@ -150,9 +141,6 @@ export async function downloadMavenXml(
let rawContent: string;
let authorization: boolean;
switch (pkgUrl.protocol) {
- case 'file:':
- rawContent = await downloadFileProtocol(pkgUrl);
- break;
case 'http:':
case 'https:':
({ authorization, body: rawContent } = await downloadHttpProtocol(
diff --git a/lib/datasource/npm/get.spec.ts b/lib/datasource/npm/get.spec.ts
index 0c444d8800bf5f..649498bfef7705 100644
--- a/lib/datasource/npm/get.spec.ts
+++ b/lib/datasource/npm/get.spec.ts
@@ -15,14 +15,9 @@ describe(getName(), () => {
beforeEach(() => {
jest.clearAllMocks();
resetMemCache();
- httpMock.setup();
hostRules.clear();
});
- afterEach(() => {
- httpMock.reset();
- });
-
describe('has bearer auth', () => {
const configs = [
`registry=https://test.org\n//test.org/:_authToken=XXX`,
diff --git a/lib/datasource/npm/index.spec.ts b/lib/datasource/npm/index.spec.ts
index 6e98e2b28d1d90..b81d2b79c13e8b 100644
--- a/lib/datasource/npm/index.spec.ts
+++ b/lib/datasource/npm/index.spec.ts
@@ -18,7 +18,6 @@ let npmResponse: any;
describe(getName(), () => {
beforeEach(() => {
jest.resetAllMocks();
- httpMock.setup();
setAdminConfig();
hostRules.clear();
resetCache();
@@ -52,7 +51,6 @@ describe(getName(), () => {
afterEach(() => {
delete process.env.RENOVATE_CACHE_NPM_MINUTES;
mockDate.reset();
- httpMock.reset();
});
it('should return null for no versions', async () => {
diff --git a/lib/datasource/nuget/index.spec.ts b/lib/datasource/nuget/index.spec.ts
index 3ed42025a9a961..61091a9ccc5d06 100644
--- a/lib/datasource/nuget/index.spec.ts
+++ b/lib/datasource/nuget/index.spec.ts
@@ -134,11 +134,6 @@ describe(getName(), () => {
jest.resetAllMocks();
hostRules.hosts.mockReturnValue([]);
hostRules.find.mockReturnValue({});
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
it(`can't detect nuget feed version`, async () => {
diff --git a/lib/datasource/orb/index.spec.ts b/lib/datasource/orb/index.spec.ts
index 590ba37c9a2f9d..4d8feff2660969 100644
--- a/lib/datasource/orb/index.spec.ts
+++ b/lib/datasource/orb/index.spec.ts
@@ -1,7 +1,7 @@
import { getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { getName } from '../../../test/util';
-import { id as datasource } from '.';
+import { OrbDatasource } from '.';
const orbData = {
data: {
@@ -26,15 +26,12 @@ const orbData = {
const baseUrl = 'https://circleci.com';
+const datasource = OrbDatasource.id;
+
describe(getName(), () => {
describe('getReleases', () => {
beforeEach(() => {
jest.clearAllMocks();
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
it('returns null for empty result', async () => {
diff --git a/lib/datasource/orb/index.ts b/lib/datasource/orb/index.ts
index 46bd7738570f0e..83098ba392322b 100644
--- a/lib/datasource/orb/index.ts
+++ b/lib/datasource/orb/index.ts
@@ -1,64 +1,57 @@
import { logger } from '../../logger';
-import * as packageCache from '../../util/cache/package';
-import { Http } from '../../util/http';
+import { cache } from '../../util/cache/package/decorator';
+import { Datasource } from '../datasource';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import type { OrbRelease } from './types';
-export const id = 'orb';
-export const defaultRegistryUrls = ['https://circleci.com/'];
-export const customRegistrySupport = false;
+export class OrbDatasource extends Datasource {
+ static readonly id = 'orb';
-const http = new Http(id);
-
-/**
- * orb.getReleases
- *
- * This function will fetch an orb from CircleCI and return all semver versions.
- */
-export async function getReleases({
- lookupName,
- registryUrl,
-}: GetReleasesConfig): Promise {
- logger.debug({ lookupName }, 'orb.getReleases()');
- const cacheNamespace = 'orb';
- const cacheKey = lookupName;
- const cachedResult = await packageCache.get(
- cacheNamespace,
- cacheKey
- );
- // istanbul ignore if
- if (cachedResult) {
- return cachedResult;
- }
- const url = `${registryUrl}graphql-unstable`;
- const body = {
- query: `{orb(name:"${lookupName}"){name, homeUrl, versions {version, createdAt}}}`,
- variables: {},
- };
- const res: OrbRelease = (
- await http.postJson<{ data: { orb: OrbRelease } }>(url, {
- body,
- })
- ).body.data.orb;
- if (!res) {
- logger.debug({ lookupName }, 'Failed to look up orb');
- return null;
+ constructor() {
+ super(OrbDatasource.id);
}
- // Simplify response before caching and returning
- const dep: ReleaseResult = {
- releases: null,
- };
- if (res.homeUrl?.length) {
- dep.homepage = res.homeUrl;
+
+ customRegistrySupport = false;
+
+ defaultRegistryUrls = ['https://circleci.com/'];
+
+ @cache({
+ namespace: `datasource-${OrbDatasource.id}`,
+ key: ({ lookupName }: GetReleasesConfig) => lookupName,
+ })
+ async getReleases({
+ lookupName,
+ registryUrl,
+ }: GetReleasesConfig): Promise {
+ const url = `${registryUrl}graphql-unstable`;
+ const body = {
+ query: `{orb(name:"${lookupName}"){name, homeUrl, versions {version, createdAt}}}`,
+ variables: {},
+ };
+ const res: OrbRelease = (
+ await this.http.postJson<{ data: { orb: OrbRelease } }>(url, {
+ body,
+ })
+ ).body.data.orb;
+ if (!res) {
+ logger.debug({ lookupName }, 'Failed to look up orb');
+ return null;
+ }
+ // Simplify response before caching and returning
+ const dep: ReleaseResult = {
+ releases: null,
+ };
+ if (res.homeUrl?.length) {
+ dep.homepage = res.homeUrl;
+ }
+ dep.homepage =
+ dep.homepage || `https://circleci.com/developer/orbs/orb/${lookupName}`;
+ dep.releases = res.versions.map(({ version, createdAt }) => ({
+ version,
+ releaseTimestamp: createdAt || null,
+ }));
+
+ logger.trace({ dep }, 'dep');
+ return dep;
}
- dep.homepage =
- dep.homepage || `https://circleci.com/developer/orbs/orb/${lookupName}`;
- dep.releases = res.versions.map(({ version, createdAt }) => ({
- version,
- releaseTimestamp: createdAt || null,
- }));
- logger.trace({ dep }, 'dep');
- const cacheMinutes = 15;
- await packageCache.set(cacheNamespace, cacheKey, dep, cacheMinutes);
- return dep;
}
diff --git a/lib/datasource/packagist/index.spec.ts b/lib/datasource/packagist/index.spec.ts
index b0fc54e79ad325..4e7e13b990b7da 100644
--- a/lib/datasource/packagist/index.spec.ts
+++ b/lib/datasource/packagist/index.spec.ts
@@ -21,7 +21,6 @@ describe(getName(), () => {
let config: any;
beforeEach(() => {
jest.resetAllMocks();
- httpMock.setup();
hostRules.find = jest.fn((input) => input);
hostRules.hosts = jest.fn(() => []);
config = {
@@ -33,10 +32,6 @@ describe(getName(), () => {
};
});
- afterEach(() => {
- httpMock.reset();
- });
-
it('supports custom registries', async () => {
config = {
registryUrls: ['https://composer.renovatebot.com'],
diff --git a/lib/datasource/pod/index.spec.ts b/lib/datasource/pod/index.spec.ts
index d5a215b15cad2b..66db34635dfcf5 100644
--- a/lib/datasource/pod/index.spec.ts
+++ b/lib/datasource/pod/index.spec.ts
@@ -19,11 +19,6 @@ describe(getName(), () => {
describe('getReleases', () => {
beforeEach(() => {
jest.resetAllMocks();
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
});
it('returns null for invalid inputs', async () => {
diff --git a/lib/datasource/pypi/index.spec.ts b/lib/datasource/pypi/index.spec.ts
index 880faa3bb05adc..c635b07517a9e8 100644
--- a/lib/datasource/pypi/index.spec.ts
+++ b/lib/datasource/pypi/index.spec.ts
@@ -22,13 +22,11 @@ describe(getName(), () => {
beforeEach(() => {
process.env = { ...OLD_ENV };
delete process.env.PIP_INDEX_URL;
- httpMock.setup();
jest.resetAllMocks();
});
afterEach(() => {
process.env = OLD_ENV;
- httpMock.reset();
});
it('returns null for empty result', async () => {
diff --git a/lib/datasource/repology/index.spec.ts b/lib/datasource/repology/index.spec.ts
index 3e61a3675ed0ea..f984ac7bd8070c 100644
--- a/lib/datasource/repology/index.spec.ts
+++ b/lib/datasource/repology/index.spec.ts
@@ -54,12 +54,6 @@ const fixtureJdk = loadFixture(`openjdk.json`);
describe(getName(), () => {
describe('getReleases', () => {
- beforeEach(() => {
- httpMock.setup();
- });
-
- afterEach(() => httpMock.reset());
-
it('returns null for empty result', async () => {
mockResolverCall('debian_stable', 'nginx', 'binname', {
status: 200,
diff --git a/lib/datasource/ruby-version/index.spec.ts b/lib/datasource/ruby-version/index.spec.ts
index d227fc8bf39363..3efbd83d385eef 100644
--- a/lib/datasource/ruby-version/index.spec.ts
+++ b/lib/datasource/ruby-version/index.spec.ts
@@ -7,14 +7,6 @@ const rubyReleasesHtml = loadFixture('releases.html');
describe(getName(), () => {
describe('getReleases', () => {
- beforeEach(() => {
- httpMock.setup();
- });
-
- afterEach(() => {
- httpMock.reset();
- });
-
it('parses real data', async () => {
httpMock
.scope('https://www.ruby-lang.org')
diff --git a/lib/datasource/rubygems/index.spec.ts b/lib/datasource/rubygems/index.spec.ts
index dce97126fb72f2..83a07492d79242 100644
--- a/lib/datasource/rubygems/index.spec.ts
+++ b/lib/datasource/rubygems/index.spec.ts
@@ -25,13 +25,11 @@ describe(getName(), () => {
beforeEach(() => {
resetCache();
- httpMock.setup();
process.env.RENOVATE_SKIP_CACHE = 'true';
jest.resetAllMocks();
});
afterEach(() => {
- httpMock.reset();
process.env.RENOVATE_SKIP_CACHE = SKIP_CACHE;
});
diff --git a/lib/datasource/sbt-package/index.spec.ts b/lib/datasource/sbt-package/index.spec.ts
index b91673e75c027c..93be280383f355 100644
--- a/lib/datasource/sbt-package/index.spec.ts
+++ b/lib/datasource/sbt-package/index.spec.ts
@@ -1,10 +1,10 @@
-import nock from 'nock';
import { getPkgReleases } from '..';
+import * as httpMock from '../../../test/http-mock';
import { getName, loadFixture } from '../../../test/util';
import * as mavenVersioning from '../../versioning/maven';
import { MAVEN_REPO } from '../maven/common';
import { parseIndexDir } from '../sbt-plugin/util';
-import * as sbtPlugin from '.';
+import * as sbtPackage from '.';
const mavenIndexHtml = loadFixture(`maven-index.html`);
const sbtPluginIndex = loadFixture(`sbt-plugins-index.html`);
@@ -13,21 +13,27 @@ describe(getName(), () => {
it('parses Maven index directory', () => {
expect(parseIndexDir(mavenIndexHtml)).toMatchSnapshot();
});
+
it('parses sbt index directory', () => {
expect(parseIndexDir(sbtPluginIndex)).toMatchSnapshot();
});
describe('getPkgReleases', () => {
beforeEach(() => {
- nock.disableNetConnect();
- nock('https://failed_repo').get('/maven/org/scalatest/').reply(404, null);
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://failed_repo')
+ .get('/maven/org/scalatest/')
+ .reply(404, null);
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/com/example/')
.reply(200, 'empty_2.12/\n');
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/com/example/empty/')
.reply(200, '');
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/org/scalatest/')
.times(3)
.reply(
@@ -40,22 +46,28 @@ describe(getName(), () => {
'scalatest-flatspec_2.12' +
'scalatest-matchers-core_2.12'
);
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/org/scalatest/scalatest/')
.reply(200, "1.2.0/");
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/org/scalatest/scalatest_2.12/')
.reply(200, "4.5.6/");
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/org/scalatest/scalatest-app_2.12/')
.reply(200, "3.2.1/");
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/org/scalatest/scalatest-flatspec_2.12/')
.reply(200, "3.2.1/");
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get('/maven2/org/scalatest/scalatest-matchers-core_2.12/')
.reply(200, "3.2.1/");
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get(
'/maven2/org/scalatest/scalatest-app_2.12/6.5.4/scalatest-app_2.12-6.5.4.pom'
)
@@ -68,7 +80,8 @@ describe(getName(), () => {
'' +
''
);
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get(
'/maven2/org/scalatest/scalatest-flatspec_2.12/6.5.4/scalatest-flatspec_2.12-6.5.4.pom'
)
@@ -80,7 +93,8 @@ describe(getName(), () => {
'' +
''
);
- nock('https://repo.maven.apache.org')
+ httpMock
+ .scope('https://repo.maven.apache.org')
.get(
'/maven2/org/scalatest/scalatest-matchers-core_2.12/6.5.4/scalatest-matchers-core_2.12-6.5.4.pom'
)
@@ -91,10 +105,12 @@ describe(getName(), () => {
''
);
- nock('https://dl.bintray.com')
+ httpMock
+ .scope('https://dl.bintray.com')
.get('/sbt/sbt-plugin-releases/com.github.gseitz/')
.reply(200, '');
- nock('https://dl.bintray.com')
+ httpMock
+ .scope('https://dl.bintray.com')
.get('/sbt/sbt-plugin-releases/org.foundweekends/sbt-bintray/')
.reply(
200,
@@ -106,7 +122,8 @@ describe(getName(), () => {
'