diff --git a/docs/usage/.pages b/docs/usage/.pages index 809fc075758dc7..912aeecce85697 100644 --- a/docs/usage/.pages +++ b/docs/usage/.pages @@ -7,6 +7,7 @@ nav: - 'Overview': 'config-overview.md' - 'Repository': 'configuration-options.md' - 'Self-hosted': 'self-hosted-configuration.md' + - 'Self-hosted experimental flags': 'self-hosted-experimental-flags.md' - 'Presets': 'config-presets.md' - 'Validation': 'config-validation.md' - ... | key-concepts diff --git a/docs/usage/config-overview.md b/docs/usage/config-overview.md index 547baa7c485c42..8ed23f918d566b 100644 --- a/docs/usage/config-overview.md +++ b/docs/usage/config-overview.md @@ -108,6 +108,8 @@ Read the [Self-hosted experimental environment variables](./self-hosted-experime Finally, there are some special environment variables that are loaded _before_ configuration parsing because they are used during logging initialization: - `LOG_CONTEXT`: a unique identifier used in each log message to track context +- `LOG_FILE`: used to enable file logging and specify the log file path +- `LOG_FILE_LEVEL`: log file logging level, defaults to `debug` - `LOG_FORMAT`: defaults to a "pretty" human-readable output, but can be changed to "json" - `LOG_LEVEL`: most commonly used to change from the default `info` to `debug` logging @@ -273,7 +275,7 @@ To get a onboarding PR from Renovate, change to Interactive mode either at the R #### Installing Renovate into selected repositories always leads to onboarding PRs -Additionally, if an Organization is installed with "Selected repositories" then the app will change `onboardingNoDeps` to `true` so that an Onboarding PR is created even if no dependencies are detected. +Additionally, if an Organization is installed with "Selected repositories" then the app will change `onboardingNoDeps` to `"enabled"` so that an Onboarding PR is created even if no dependencies are detected. ### Fork Processing diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 7bca3ce841f580..fda83b664f807f 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -62,7 +62,7 @@ Consider this example: "labels": ["dependencies"], "packageRules": [ { - "matchPackagePatterns": ["eslint"], + "matchPackageNames": ["/eslint/"], "labels": ["linting"] }, { @@ -1080,7 +1080,7 @@ If you want to approve _specific_ packages, set `dependencyDashboardApproval` to { "packageRules": [ { - "matchPackagePatterns": ["^@package-name"], + "matchPackageNames": ["/^@package-name/"], "dependencyDashboardApproval": true } ] @@ -1161,7 +1161,7 @@ To disable Renovate for all `eslint` packages, you can configure a package rule { "packageRules": [ { - "matchPackagePatterns": ["^eslint"], + "matchPackageNames": ["eslint**"], "enabled": false } ] @@ -2170,7 +2170,7 @@ Consider this example: "labels": ["dependencies"], "packageRules": [ { - "matchPackagePatterns": ["eslint"], + "matchPackageNames": ["/eslint/"], "labels": ["linting"] } ] @@ -2408,14 +2408,14 @@ Here is an example if you want to group together all packages starting with `esl { "packageRules": [ { - "matchPackagePatterns": ["^eslint"], + "matchPackageNames": ["eslint**"], "groupName": "eslint packages" } ] } ``` -Note how the above uses `matchPackagePatterns` with a regex value. +Note how the above uses `matchPackageNames` with a prefix pattern. Here's an example config to limit the "noisy" `aws-sdk` package to weekly updates: @@ -2432,24 +2432,22 @@ Here's an example config to limit the "noisy" `aws-sdk` package to weekly update For Maven dependencies, the package name is ``, e.g. `"matchPackageNames": ["com.thoughtworks.xstream:xstream"]` -Note how the above uses `matchPackageNames` instead of `matchPackagePatterns` because it is an exact match package name. -This is the equivalent of defining `"matchPackagePatterns": ["^aws\-sdk$"]`. -However you can mix together both `matchPackageNames` and `matchPackagePatterns` in the same package rule and the rule will be applied if _either_ match. +Note how the above uses an exact match string for `matchPackageNames` instead of a pattern +However you can mix together both patterns and exact matches in the same package rule and the rule will be applied if _either_ match. Example: ```json { "packageRules": [ { - "matchPackageNames": ["neutrino"], - "matchPackagePatterns": ["^@neutrino/"], + "matchPackageNames": ["neutrino", "@neutrino/**"], "groupName": "neutrino monorepo" } ] } ``` -The above rule will group together the `neutrino` package and any package matching `@neutrino/*`. +The above rule will group together the `neutrino` package and any package starting with `@neutrino/`. File name matches are convenient to use if you wish to apply configuration rules to certain package or lock files using patterns. For example, if you have an `examples` directory and you want all updates to those examples to use the `chore` prefix instead of `fix`, then you could add this configuration: @@ -2574,86 +2572,6 @@ Instead you should do `> 13 months`. Use this field if you want to limit a `packageRule` to certain `depType` values. Invalid if used outside of a `packageRule`. -### excludeDepNames - -### excludeDepPatterns - -### excludeDepPrefixes - -### excludePackageNames - -**Important**: Do not mix this up with the option `ignoreDeps`. -Use `ignoreDeps` instead if all you want to do is have a list of package names for Renovate to ignore. - -Use `excludePackageNames` if you want to have one or more exact name matches excluded in your package rule. -See also `matchPackageNames`. - -```json -{ - "packageRules": [ - { - "matchPackagePatterns": ["^eslint"], - "excludePackageNames": ["eslint-foo"] - } - ] -} -``` - -The above will match all package names starting with `eslint` but exclude the specific package `eslint-foo`. - -### excludePackagePatterns - -Use this field if you want to have one or more package name patterns excluded in your package rule. -See also `matchPackagePatterns`. - -```json -{ - "packageRules": [ - { - "matchPackagePatterns": ["^eslint"], - "excludePackagePatterns": ["^eslint-foo"] - } - ] -} -``` - -The above will match all package names starting with `eslint` but exclude ones starting with `eslint-foo`. - -### excludePackagePrefixes - -Use this field if you want to have one or more package name prefixes excluded in your package rule, without needing to write a regex. -See also `matchPackagePrefixes`. - -```json -{ - "packageRules": [ - { - "matchPackagePrefixes": ["eslint"], - "excludePackagePrefixes": ["eslint-foo"] - } - ] -} -``` - -The above will match all package names starting with `eslint` but exclude ones starting with `eslint-foo`. - -### excludeRepositories - -Use this field to restrict rules to a particular repository. e.g. - -```json -{ - "packageRules": [ - { - "excludeRepositories": ["literal/repo", "/^some/.*$/", "**/*-archived"], - "enabled": false - } - ] -} -``` - -This field supports Regular Expressions if they begin and end with `/`, otherwise it will use `minimatch`. - ### matchCategories Use `matchCategories` to restrict rules to a particular language or group. @@ -2676,6 +2594,8 @@ The categories can be found in the [manager documentation](modules/manager/index } ``` +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + ### matchRepositories Use this field to restrict rules to a particular repository. e.g. @@ -2702,7 +2622,7 @@ Use this field to restrict rules to a particular branch. e.g. "packageRules": [ { "matchBaseBranches": ["main"], - "excludePackagePatterns": ["^eslint"], + "matchPackageNames": ["eslint**"], "enabled": false } ] @@ -2716,13 +2636,15 @@ This field also supports Regular Expressions if they begin and end with `/`. e.g "packageRules": [ { "matchBaseBranches": ["/^release/.*/"], - "excludePackagePatterns": ["^eslint"], + "matchPackageNames": ["eslint**"], "enabled": false } ] } ``` +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + ### matchManagers Use this field to restrict rules to a particular package manager. e.g. @@ -2741,6 +2663,8 @@ Use this field to restrict rules to a particular package manager. e.g. For the full list of available managers, see the [Supported Managers](modules/manager/index.md#supported-managers) documentation. +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + ### matchMessage For log level remapping, use this field to match against the particular log messages. @@ -2762,6 +2686,8 @@ Use this field to restrict rules to a particular datasource. e.g. } ``` +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + ### matchCurrentValue This option is matched against the `currentValue` field of a dependency. @@ -2786,7 +2712,7 @@ Regular Expressions must begin and end with `/`. { "packageRules": [ { - "matchPackagePatterns": ["io.github.resilience4j"], + "matchPackageNames": ["io.github.resilience4j**"], "matchCurrentValue": "/^1\\./" } ] @@ -2800,7 +2726,7 @@ Use the syntax `!/ /` like this: { "packageRules": [ { - "matchPackagePatterns": ["io.github.resilience4j"], + "matchPackageNames": ["io.github.resilience4j**"], "matchCurrentValue": "!/^0\\./" } ] @@ -2841,7 +2767,7 @@ For example, the following enforces that only `1.*` versions will be used: { "packageRules": [ { - "matchPackagePatterns": ["io.github.resilience4j"], + "matchPackageNames": ["io.github.resilience4j**"], "matchCurrentVersion": "/^1\\./" } ] @@ -2855,7 +2781,7 @@ Use the syntax `!/ /` like this: { "packageRules": [ { - "matchPackagePatterns": ["io.github.resilience4j"], + "matchPackageNames": ["io.github.resilience4j**"], "matchCurrentVersion": "!/^0\\./" } ] @@ -2909,14 +2835,12 @@ The following example matches any file in directories starting with `app/`: It is recommended that you avoid using "negative" globs, like `**/!(package.json)`, because such patterns might still return true if they match against the lock file name (e.g. `package-lock.json`). +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + ### matchDepNames This field behaves the same as `matchPackageNames` except it matches against `depName` instead of `packageName`. -### matchDepPatterns - -### matchDepPrefixes - ### matchNewValue This option is matched against the `newValue` field of a dependency. @@ -2941,7 +2865,7 @@ Regular Expressions must begin and end with `/`. { "packageRules": [ { - "matchPackagePatterns": ["io.github.resilience4j"], + "matchPackageNames": ["io.github.resilience4j**"], "matchNewValue": "/^1\\./" } ] @@ -2955,7 +2879,7 @@ Use the syntax `!/ /` like this: { "packageRules": [ { - "matchPackagePatterns": ["io.github.resilience4j"], + "matchPackageNames": ["io.github.resilience4j**"], "matchNewValue": "!/^0\\./" } ] @@ -2966,13 +2890,17 @@ For more details on this syntax see Renovate's [string pattern matching document ### matchPackageNames -Use this field if you want to have one or more exact name matches in your package rule. -See also `excludePackageNames`. +Use this field to match against the `packageName` field. +This matching can be an exact match, Glob match, or Regular Expression match. -```json +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). +Note that Glob matching (including exact name matching) is case-insensitive. + +```json title="exact name match" { "packageRules": [ { + "matchDatasources": ["npm"], "matchPackageNames": ["angular"], "rangeStrategy": "pin" } @@ -2980,76 +2908,34 @@ See also `excludePackageNames`. } ``` -The above will configure `rangeStrategy` to `pin` only for the package `angular`. - - -!!! note - `matchPackageNames` will try matching `packageName` first and then fall back to matching `depName`. - If the fallback is used, Renovate will log a warning, because the fallback will be removed in a future release. - Use `matchDepNames` instead. - -### matchPackagePatterns - -Use this field if you want to have one or more package names patterns in your package rule. -See also `excludePackagePatterns`. +The above will configure `rangeStrategy` to `pin` only for the npm package `angular`. -```json +```json title="prefix match using Glob" { "packageRules": [ { - "matchPackagePatterns": ["^angular"], + "matchPackagePatterns": ["^angular", "!@angular/abc"], "rangeStrategy": "replace" } ] } ``` -The above will configure `rangeStrategy` to `replace` for any package starting with `angular`. - - -!!! note - `matchPackagePatterns` will try matching `packageName` first and then fall back to matching `depName`. - If the fallback is used, Renovate will log a warning, because the fallback will be removed in a future release. - Use `matchDepPatterns` instead. - -### matchPackagePrefixes +The above will set a replaceStrategy for any npm package which starts with `@angular/` except `@angular/abc`. -Use this field to match a package prefix without needing to write a regex expression. -See also `excludePackagePrefixes`. - -```json +```json title="pattern match using RegEx" { "packageRules": [ { - "matchPackagePrefixes": ["angular"], - "rangeStrategy": "replace" + "matchDatasources": ["npm"], + "matchPackageNames": ["/^angular/"], + "groupName": "Angular" } ] } ``` -Like the earlier `matchPackagePatterns` example, the above will configure `rangeStrategy` to `replace` for any package starting with `angular`. - - -!!! note - `matchPackagePrefixes` will try matching `packageName` first and then fall back to matching `depName`. - If the fallback is used, Renovate will log a warning, because the fallback will be removed in a future release. - Use `matchDepPatterns` instead. - -### matchSourceUrlPrefixes - -Here's an example of where you use this to group together all packages from the `renovatebot` GitHub org: - -```json -{ - "packageRules": [ - { - "matchSourceUrlPrefixes": ["https://github.com/renovatebot/"], - "groupName": "All renovate packages" - } - ] -} -``` +The above will group together any npm package which starts with the string `angular`. ### matchSourceUrls @@ -3066,6 +2952,8 @@ Here's an example of where you use this to group together all packages from the } ``` +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + ### matchUpdateTypes Use `matchUpdateTypes` to match rules against types of updates. @@ -3082,6 +2970,8 @@ For example to apply a special label to `major` updates: } ``` +For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md). + !!! warning Packages that follow SemVer are allowed to make breaking changes in _any_ `0.x` version, even `patch` and `minor`. @@ -3179,7 +3069,7 @@ For example, the following package rule can be used to replace the registry for "packageRules": [ { "matchDatasources": ["docker"], - "matchPackagePatterns": ["^docker\\.io/.+"], + "matchPackageNames": ["docker.io/**"], "replacementNameTemplate": "{{{replace 'docker\\.io/' 'ghcr.io/' packageName}}}" } ] @@ -3194,13 +3084,13 @@ Or, to add a registry prefix to any `docker` images that do not contain an expli { "description": "official images", "matchDatasources": ["docker"], - "matchPackagePatterns": ["^[a-z-]+$"], + "matchPackageNames": ["/^[a-z-]+$/"], "replacementNameTemplate": "some.registry.org/library/{{{packageName}}}" }, { "description": "non-official images", "matchDatasources": ["docker"], - "matchPackagePatterns": ["^[a-z-]+/[a-z-]+$"], + "matchPackageNames": ["/^[a-z-]+/[a-z-]+$/"], "replacementNameTemplate": "some.registry.org/{{{packageName}}}" } ] @@ -3992,15 +3882,6 @@ The above config will suppress the comment which is added to a PR whenever you c It is only recommended to configure this field if you wish to use the `schedules` feature and want to write them in your local timezone. Please see the above link for valid timezone names. -## transitiveRemediation - -When enabled, Renovate tries to remediate vulnerabilities even if they exist only in transitive dependencies. - -Applicable only for GitHub platform (with vulnerability alerts enabled) and `npm` manager. -When the `lockfileVersion` is higher than `1` in `package-lock.json`, remediations are only possible when changes are made to `package.json`. - -This is considered a feature flag with the aim to remove it and default to this behavior once it has been more widely tested. - ## updateInternalDeps Renovate defaults to skipping any internal package dependencies within monorepos. diff --git a/docs/usage/examples/self-hosting.md b/docs/usage/examples/self-hosting.md index 42d25404146694..2ac4ae27bd884b 100644 --- a/docs/usage/examples/self-hosting.md +++ b/docs/usage/examples/self-hosting.md @@ -248,7 +248,7 @@ module.exports = { }; ``` -Here change the `logFile` and `repositories` to something appropriate. +Here change the `repositories` to something appropriate. Also replace `gitlab-token` value with the one created during the previous step. If you're running against GitHub Enterprise Server, then change the `gitlab` values in the example to the equivalent GitHub ones. diff --git a/docs/usage/faq.md b/docs/usage/faq.md index 6ee4c111693683..3ff913c2d72c84 100644 --- a/docs/usage/faq.md +++ b/docs/usage/faq.md @@ -120,7 +120,7 @@ The basic idea is that you create a new `packageRules` entry and describe what k { "packageRules": [ { - "matchPackagePatterns": ["^jest"], + "matchPackageNames": ["jest"], "matchUpdateTypes": ["major"], "dependencyDashboardApproval": true } @@ -223,13 +223,13 @@ e.g. ### Apply a rule, but only for packages starting with `abc` -Do the same as above, but instead of using `matchPackageNames`, use `matchPackagePatterns` and a regex: +Do the same as above, but instead of an exact match, use a glob prefix: ```json { "packageRules": [ { - "matchPackagePatterns": "^abc", + "matchPackageNames": "abc**", "assignees": ["importantreviewer"] } ] @@ -244,7 +244,7 @@ As above, but apply a `groupName`: { "packageRules": [ { - "matchPackagePatterns": "^abc", + "matchPackageNames": "abc**", "groupName": ["abc packages"] } ] diff --git a/docs/usage/key-concepts/automerge.md b/docs/usage/key-concepts/automerge.md index 43095d66b7ded7..2f24b7ae2ebb04 100644 --- a/docs/usage/key-concepts/automerge.md +++ b/docs/usage/key-concepts/automerge.md @@ -56,7 +56,7 @@ But in many cases the new version(s) will pass tests, and if so then there's rea "packageRules": [ { "matchDepTypes": ["devDependencies"], - "matchPackagePatterns": ["lint", "prettier"], + "matchPackageNames": ["lint", "prettier"], "automerge": true } ] @@ -263,7 +263,7 @@ To turn off automerge for all dependencies of a selected repository, you need to "extends": ["local>org-name/.github:renovate-config"], "packageRules": [ { - "matchPackagePatterns": ["*"], + "matchPackageNames": ["*"], "automerge": false } ] diff --git a/docs/usage/key-concepts/dashboard.md b/docs/usage/key-concepts/dashboard.md index a564d63dc9f41f..1a20881912a1f1 100644 --- a/docs/usage/key-concepts/dashboard.md +++ b/docs/usage/key-concepts/dashboard.md @@ -121,7 +121,7 @@ If you want to approve specific packages, set `dependencyDashboardApproval` to ` { "packageRules": [ { - "matchPackagePatterns": ["^@somescope"], + "matchPackageName": ["@somescope/**"], "dependencyDashboardApproval": true } ] diff --git a/docs/usage/modules/versioning/index.md b/docs/usage/modules/versioning/index.md index eab2373bcb1b40..e9bad3fa2cc8a2 100644 --- a/docs/usage/modules/versioning/index.md +++ b/docs/usage/modules/versioning/index.md @@ -23,7 +23,7 @@ Configuring or overriding the default `versioning` can be extra helpful for ecos - Although you can reconfigure versioning per-manager or per-datasource, you probably don't need such a broad change - More commonly you would need to configure `versioning` for individual packages or potentially package patterns -- The best way to do this is with `packageRules`, with a combination of `matchManagers`, `matchDatasources`, `matchPackageNames` and `matchPackagePatterns`. +- The best way to do this is with `packageRules`, with a combination of `matchManagers`, `matchDatasources`, and `matchPackageNames`. Avoid configuring `versioning` in a rule that also uses `matchUpdateTypes`, as the update types aren't known at the time the `versioning` is applied ## Examples of versioning overrides diff --git a/docs/usage/noise-reduction.md b/docs/usage/noise-reduction.md index 468c5383dc5747..d88a39671a5eff 100644 --- a/docs/usage/noise-reduction.md +++ b/docs/usage/noise-reduction.md @@ -31,14 +31,14 @@ In that case you might create a config like this: { "packageRules": [ { - "matchPackagePatterns": ["eslint"], + "matchPackageNames": ["/eslint/"], "groupName": "eslint" } ] } ``` -By setting `matchPackagePatterns` to "eslint", it means that any package with ESLint anywhere in its name will be grouped into a `renovate/eslint` branch and related PR. +By setting `matchPackageNames` to `/eslint/`, it means that any package with eslint anywhere in its name will be grouped into a `renovate/eslint` branch and related PR. ### Be smart about grouping dependencies @@ -91,7 +91,7 @@ You don't want to get too far behind, so how about we update `eslint` packages o { "packageRules": [ { - "matchPackagePatterns": ["eslint"], + "matchPackageNames": ["/eslint/"], "groupName": "eslint", "schedule": ["on the first day of the month"] } @@ -105,7 +105,7 @@ Or perhaps at least weekly: { "packageRules": [ { - "matchPackagePatterns": ["eslint"], + "matchPackageNames": ["/eslint/"], "groupName": "eslint", "schedule": ["before 4am on monday"] } @@ -165,7 +165,7 @@ Let's automerge it if all the linting updates pass: { "packageRules": [ { - "matchPackagePatterns": ["eslint"], + "matchPackageNames": ["/eslint/"], "groupName": "eslint", "schedule": ["before 4am on monday"], "automerge": true, diff --git a/docs/usage/python.md b/docs/usage/python.md index 031d49640dfda8..6b3751c2134360 100644 --- a/docs/usage/python.md +++ b/docs/usage/python.md @@ -23,6 +23,13 @@ Legacy versions with the `===` prefix are ignored. 1. Renovate searches for the latest version on [PyPI](https://pypi.org/) to decide if there are upgrades 1. If the source package includes a GitHub URL as its source, and has a "changelog" file _or_ uses GitHub releases, a Release Note will be embedded in the generated PR +## Package name matching + +Your `matchPackageName` or `matchPackagePattern` rules will be matching against normalized names. +So if you have specified package `some.package` or `ANOTHER_DEP` in your package files (`requirements.txt`, `pyproject.toml`), they will be treated as `some-package` and `another-dep` respecitvely. +Not only they will be case insensitive but will replace any amount `._-` to a single `-`. +[Consult Python packaging documentation for the specification](https://packaging.python.org/en/latest/specifications/name-normalization/). + ## Alternate registries By default Renovate checks for upgrades on the `pypi.org` registry. diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index 66f2f3fc9341de..fe18d5dcb39c50 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -566,6 +566,18 @@ If set to a string value, Renovate will log warnings with the `encryptedWarning` Default execution timeout in minutes for child processes Renovate creates. If this option is not set, Renovate will fallback to 15 minutes. +## experimentalFlags + +The `experimentalFlags` configuration option helps you manage some of the experimental settings within Renovate. + +```javascript +module.exports = { + experimentalFlags: ['disableDockerHubTags'], +}; +``` + +Read [Self-Hosted Experimental Flags](./self-hosted-experimental-flags.md) for the full list of flags supported by Renovate. + ## exposeAllEnv To keep you safe, Renovate only passes a limited set of environment variables to package managers. @@ -757,10 +769,6 @@ When you set `inheritConfigStrict=true` then Renovate will abort the run and rai `logContext` is included with each log entry only if `logFormat="json"` - it is not included in the pretty log output. If left as default (null), a random short ID will be selected. -## logFile - -## logFileLevel - ## mergeConfidenceDatasources This feature is applicable only if you have an access token for Mend's Merge Confidence API. @@ -846,8 +854,12 @@ Falls back to `renovate.json` if the name provided is not valid. ## onboardingNoDeps -Set this to `true` if you want Renovate to create an onboarding PR even if no dependencies are found. -Otherwise, Renovate skips onboarding a repository if it finds no dependencies in it. +The default `auto` setting is converted to `disabled` if `autodiscoverRepositories` is `true`, or converted to `enabled` if false. + +In other words, the default behavior is: + +- If you run Renovate on discovered repositories then it will skip onboarding those without dependencies detected, but +- If you run Renovate on _specific_ repositories then Renovate will onboard all such repositories even if no dependencies are found ## onboardingPrTitle diff --git a/docs/usage/self-hosted-experimental-flags.md b/docs/usage/self-hosted-experimental-flags.md new file mode 100644 index 00000000000000..689245756d6189 --- /dev/null +++ b/docs/usage/self-hosted-experimental-flags.md @@ -0,0 +1,87 @@ +# Self-hosted experimental flags + +The following flags are "experimental" because they: + +- are not commonly needed +- are typically an effort to work around some other service's or platform's problem +- can be removed at any time +- are flags for Renovate's internal use to validate they work as intended + +Experimental flags which are commonly used and for which there is no external solution in sight can be converted to an official configuration option by the Renovate bot developers. + +Use these experimental flags at your own risk. +These flags may be removed or have their behavior changed in **any** version. +We will try to keep breakage to a minimum, but make no guarantees that an experimental flag will keep working. + +## `disableDockerHubTags` + +By default, Renovate uses the _Docker Hub_ API (`https://hub.docker.com`) to fetch tags. + +But when you set the `disableDockerHubTags` flag, Renovate will instead fetch tags from the normal _Docker_ API, for images pulled from `https://index.docker.io`. + +Example usage: + +```js +experimentalFlags: ['disableDockerHubTags']; +``` + +## `execGpidHandle` + +If added to the `experimentalFlags` list, Renovate will terminate the whole process group of a terminated child process spawned by Renovate. + +Example usage: + +```js +experimentalFlags: ['execGpidHandle']; +``` + +## `noMavenPomCheck` + +If added to the `experimentalFlags` list, Renovate will skip its default artifacts filter check in the Maven datasource. +Skipping the check will speed things up, but may result in versions being returned which don't properly exist on the server. + +Example usage: + +```js +experimentalFlags: ['noMavenPomCheck']; +``` + +## `nugetDownloadNupkgs` + +If added to the `experimentalFlags` list, Renovate will download `nupkg` files for determining package metadata. + +Example usage: + +```js +experimentalFlags: ['nugetDownloadNupkgs']; +``` + +## `repoCacheForceLocal` + +If added to the `experimentalFlags` list, Renovate will persist repository cache locally after uploading to S3. + +Example usage: + +```js +experimentalFlags: ['repoCacheForceLocal']; +``` + +## `useOpenpgp` + +Use `openpgp` instead of `kbpgp` for `PGP` decryption. + +Example usage: + +```js +experimentalFlags: ['useOpenpgp']; +``` + +## `yarnProxy` + +Configure global Yarn proxy settings if HTTP proxy environment variables are detected. + +Example usage: + +```js +experimentalFlags: ['yarnProxy']; +``` diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index b1800d4b6af51b..4e30552920efef 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -19,15 +19,6 @@ We will try to keep breakage to a minimum, but make no guarantees that an experi If set, Renovate will export OpenTelemetry data to the supplied endpoint. For more information see [the OpenTelemetry docs](opentelemetry.md). -## `RENOVATE_CACHE_NPM_MINUTES` - -If set to any integer, Renovate will use this integer instead of the default npm cache time (15 minutes) for the npm datasource. - -## `RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK` - -If set to any value, Renovate will skip its default artifacts filter check in the Maven datasource. -Skipping the check will speed things up, but may result in versions being returned which don't properly exist on the server. - ## `RENOVATE_PAGINATE_ALL` If set to any value, Renovate will always paginate requests to GitHub fully, instead of stopping after 10 pages. @@ -39,23 +30,11 @@ You can set the config file Renovate should read with the `RENOVATE_CONFIG_FILE` The process that runs Renovate must have the correct permissions to delete the config file. -## `RENOVATE_X_DOCKER_HUB_TAGS` - -If set to any value, Renovate will use the Docker Hub API (`https://hub.docker.com`) to fetch tags instead of the normal Docker API for images pulled from `https://index.docker.io`. - ## `RENOVATE_X_DOCKER_MAX_PAGES` If set to an integer, Renovate will use this as max page number for docker tags lookup on docker registries, instead of the default 20 pages. This is useful for registries which ignores the `n` parameter in the query string and only return 50 tags per page. -## `RENOVATE_X_EAGER_GLOBAL_EXTENDS` - -Resolve and merge `globalExtends` presets before other global config, instead of after. - -## `RENOVATE_X_EXEC_GPID_HANDLE` - -If set, Renovate will terminate the whole process group of a terminated child process spawned by Renovate. - ## `RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS` If set to an positive integer, Renovate will use this as the number of attempts to check if a merge request on GitLab is mergeable before trying to automerge. @@ -90,10 +69,6 @@ Suppress the default warning when a deprecated version of Node.js is used to run Skip initializing `RE2` for regular expressions and instead use Node-native `RegExp` instead. -## `RENOVATE_X_NUGET_DOWNLOAD_NUPKGS` - -If set to any value, Renovate will download `nupkg` files for determining package metadata. - ## `RENOVATE_X_PLATFORM_VERSION` Specify this string for Renovate to skip API checks and provide GitLab/Bitbucket server version directly. @@ -109,10 +84,6 @@ If set, Renovate will rewrite GitHub Enterprise Server's pagination responses to !!! note For the GitHub Enterprise Server platform only. -## `RENOVATE_X_REPO_CACHE_FORCE_LOCAL` - -If set, Renovate will persist repository cache locally after uploading to S3. - ## `RENOVATE_X_S3_ENDPOINT` If set, Renovate will use this string as the `endpoint` when instantiating the AWS S3 client. @@ -133,11 +104,3 @@ Don't combine with `redisUrl`, Redis would be preferred over SQlite. ## `RENOVATE_X_SUPPRESS_PRE_COMMIT_WARNING` Suppress the pre-commit support warning in PR bodies. - -## `RENOVATE_X_USE_OPENPGP` - -Use `openpgp` instead of `kbpgp` for `PGP` decryption. - -## `RENOVATE_X_YARN_PROXY` - -Configure global Yarn proxy settings if HTTP proxy environment variables are detected. diff --git a/docs/usage/string-pattern-matching.md b/docs/usage/string-pattern-matching.md index 61ad36545a9f7c..c22001a58a4817 100644 --- a/docs/usage/string-pattern-matching.md +++ b/docs/usage/string-pattern-matching.md @@ -5,6 +5,33 @@ Renovate string matching syntax for some configuration options allows you, as us - [`minimatch`](https://github.com/isaacs/minimatch) glob patterns, including exact strings matches - regular expression (regex) patterns +In cases where there are potentially multiple _inputs_, e.g. managers can have multiple categories, then the matcher will return `true` if _any_ of them match. + +## Special case: Match everything + +The value `*` is a special case which means "match everything". +It is not valid to combine `*` with any other positive or negative match. + +```json title="Example of valid wildcard use" +{ + "allowedEnv": ["*"] +} +``` + +```json title="Example of invalid wildcard use with additional match" +{ + "allowedEnv": ["*", "ABC"] +} +``` + +```json title="Example of invalid wildcard use with negation" +{ + "allowedEnv": ["*", "!ABC"] +} +``` + +In the latter case, the `*` can be ommitted and achieve the same thing. + ## Regex matching A valid regex pattern: @@ -76,6 +103,13 @@ For example, the pattern `["/^abc/", "!/^abcd/", "!/abce/"]`: - matches `"abc"` and `"abcf"` - does _not_ match `"foo"`, `"abcd"`, `"abce"`, or `"abcdef"` +If you find yourself in a situation where you need to positive-match a string which starts with `!`, then you need to do so using a regular expression pattern. +For example, `["/^!abc$/"]` will positively match against the string `"!abc"`. + +One limitation of negative matching is when there may be multiple inputs to match against. +For example, a manager may have multiple categories, such as `java` and `docker`. +If you have a rule such as `"matchCategories": ["!docker"]` then this will return `true` because the `java` category satisfies this rule. + ## Usage in Renovate configuration options Renovate has evolved its approach to string pattern matching over time, but this means that existing configurations may have a mix of approaches and not be entirely consistent with each other. diff --git a/lib/config/__snapshots__/migration.spec.ts.snap b/lib/config/__snapshots__/migration.spec.ts.snap index b3f0b3deb66552..56119458e67a1e 100644 --- a/lib/config/__snapshots__/migration.spec.ts.snap +++ b/lib/config/__snapshots__/migration.spec.ts.snap @@ -58,22 +58,20 @@ exports[`config/migration it migrates nested packageRules 1`] = ` }, { "automerge": true, - "excludePackageNames": [ - "@types/react-table", - ], "groupName": "definitelyTyped", - "matchPackagePrefixes": [ - "@types/", + "matchPackageNames": [ + "!@types/react-table", + "@types/**", ], }, { "automerge": false, - "excludePackageNames": [ - "@types/react-table", - ], "matchDepTypes": [ "dependencies", ], + "matchPackageNames": [ + "!@types/react-table", + ], }, ], } @@ -190,23 +188,22 @@ exports[`config/migration migrateConfig(config, parentConfig) migrates config 1` ], }, { - "excludePackageNames": "foo", "groupName": "angular packages", - "matchPackagePatterns": "^(@angular|typescript)", + "matchPackageNames": [ + "/^(@angular|typescript)/", + ], }, { "groupName": "foo", - "matchPackagePatterns": [ - "^foo", + "matchPackageNames": [ + "/^foo/", ], }, { "enabled": false, "matchPackageNames": [ "angular", - ], - "matchPackagePatterns": [ - "ang", + "/ang/", ], }, { @@ -399,7 +396,9 @@ exports[`config/migration migrateConfig(config, parentConfig) overrides existing "major": { "automerge": false, }, - "matchPackagePatterns": "^(@angular|typescript)", + "matchPackageNames": [ + "/^(@angular|typescript)/", + ], "minor": { "automerge": false, }, diff --git a/lib/config/__snapshots__/validation.spec.ts.snap b/lib/config/__snapshots__/validation.spec.ts.snap index 3887cf94919695..4bfe7e328db719 100644 --- a/lib/config/__snapshots__/validation.spec.ts.snap +++ b/lib/config/__snapshots__/validation.spec.ts.snap @@ -54,11 +54,7 @@ exports[`config/validation validateConfig(config) errors for all types 1`] = ` "topic": "Configuration Error", }, { - "message": "Configuration option \`packageRules[3].matchDepPatterns\` should be a list (Array)", - "topic": "Configuration Error", - }, - { - "message": "Configuration option \`packageRules[3].matchPackagePatterns\` should be a list (Array)", + "message": "Configuration option \`packageRules[2].matchPackageNames\` should be a list (Array)", "topic": "Configuration Error", }, { @@ -66,15 +62,7 @@ exports[`config/validation validateConfig(config) errors for all types 1`] = ` "topic": "Configuration Error", }, { - "message": "Invalid configuration option: packageRules[1].foo", - "topic": "Configuration Error", - }, - { - "message": "Invalid regExp for packageRules[3].excludeDepPatterns: \`abc ([a-z]+) ([a-z]+))\`", - "topic": "Configuration Error", - }, - { - "message": "Invalid regExp for packageRules[3].excludePackagePatterns: \`abc ([a-z]+) ([a-z]+))\`", + "message": "Invalid configuration option: packageRules[0].foo", "topic": "Configuration Error", }, { @@ -90,7 +78,7 @@ exports[`config/validation validateConfig(config) errors for all types 1`] = ` "topic": "Configuration Error", }, { - "message": "packageRules[1]: Each packageRule must contain at least one match* or exclude* selector. Rule: {"foo":1}", + "message": "packageRules[0]: Each packageRule must contain at least one match* or exclude* selector. Rule: {"foo":1}", "topic": "Configuration Error", }, { @@ -220,7 +208,11 @@ exports[`config/validation validateConfig(config) returns nested errors 1`] = ` "topic": "Configuration Error", }, { - "message": "Invalid regExp for packageRules[0].excludePackagePatterns: \`abc ([a-z]+) ([a-z]+))\`", + "message": "Invalid regExp for packageRules[0].matchPackageNames: \`!/abc ([a-z]+) ([a-z]+))/\`", + "topic": "Configuration Error", + }, + { + "message": "Invalid regExp for packageRules[0].matchPackageNames: \`/abc ([a-z]+) ([a-z]+))/\`", "topic": "Configuration Error", }, ] @@ -268,7 +260,7 @@ exports[`config/validation validateConfig(config) warns if hostType has the wron exports[`config/validation validateConfig(config) warns if only selectors in packageRules 1`] = ` [ { - "message": "packageRules[0]: Each packageRule must contain at least one non-match* or non-exclude* field. Rule: {"matchDepTypes":["foo"],"excludePackageNames":["bar"]}", + "message": "packageRules[0]: Each packageRule must contain at least one non-match* or non-exclude* field. Rule: {"matchDepTypes":["foo"],"matchPackageNames":["bar"]}", "topic": "Configuration Error", }, ] diff --git a/lib/config/decrypt.ts b/lib/config/decrypt.ts index 09443ad5f4c835..3c9f4f4e6f3581 100644 --- a/lib/config/decrypt.ts +++ b/lib/config/decrypt.ts @@ -22,10 +22,9 @@ export async function tryDecrypt( ): Promise { let decryptedStr: string | null = null; if (privateKey?.startsWith('-----BEGIN PGP PRIVATE KEY BLOCK-----')) { - const decryptedObjStr = - process.env.RENOVATE_X_USE_OPENPGP === 'true' - ? await tryDecryptOpenPgp(privateKey, encryptedStr) - : await tryDecryptKbPgp(privateKey, encryptedStr); + const decryptedObjStr = GlobalConfig.getExperimentalFlag('useOpenpgp') + ? await tryDecryptOpenPgp(privateKey, encryptedStr) + : await tryDecryptKbPgp(privateKey, encryptedStr); if (decryptedObjStr) { decryptedStr = validateDecryptedValue(decryptedObjStr, repository); } diff --git a/lib/config/decrypt/openpgp.spec.ts b/lib/config/decrypt/openpgp.spec.ts index 272b758df0c7d2..5af31cdc32b886 100644 --- a/lib/config/decrypt/openpgp.spec.ts +++ b/lib/config/decrypt/openpgp.spec.ts @@ -11,10 +11,6 @@ describe('config/decrypt/openpgp', () => { describe('decryptConfig()', () => { let config: RenovateConfig; - beforeAll(() => { - process.env.RENOVATE_X_USE_OPENPGP = 'true'; - }); - beforeEach(() => { jest.resetModules(); config = {}; @@ -22,7 +18,7 @@ describe('config/decrypt/openpgp', () => { }); it('rejects invalid PGP message', async () => { - GlobalConfig.set({ privateKey }); + GlobalConfig.set({ privateKey, experimentalFlags: ['useOpenpgp'] }); config.encrypted = { token: 'long-but-wrong-wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24', @@ -63,7 +59,7 @@ describe('config/decrypt/openpgp', () => { }); it('handles PGP org constraint', async () => { - GlobalConfig.set({ privateKey }); + GlobalConfig.set({ privateKey, experimentalFlags: ['useOpenpgp'] }); config.encrypted = { token: 'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10', @@ -77,7 +73,7 @@ describe('config/decrypt/openpgp', () => { }); it('handles PGP multi-org constraint', async () => { - GlobalConfig.set({ privateKey }); + GlobalConfig.set({ privateKey, experimentalFlags: ['useOpenpgp'] }); config.encrypted = { token: 'wcFMAw+4H7SgaqGOAQ//Yk4RTQoLEhO0TKxN2IUBrCi88ts+CG1SXKeL06sJ2qikN/3n2JYAGGKgkHRICfu5dOnsjyFdLJ1XWUrbsM3XgVWikMbrmzD1Xe7N5DsoZXlt4Wa9pZ+IkZuE6XcKKu9whIJ22ciEwCzFwDmk/CBshdCCVVQ3IYuM6uibEHn/AHQ8K15XhraiSzF6DbJpevs5Cy7b5YHFyE936H25CVnouUQnMPsirpQq3pYeMq/oOtV/m4mfRUUQ7MUxvtrwE4lq4hLjFu5n9rwlcqaFPl7I7BEM++1c9LFpYsP5mTS7hHCZ9wXBqER8fa3fKYx0bK1ihCpjP4zUkR7P/uhWDArXamv7gHX2Kj/Qsbegn7KjTdZlggAmaJl/CuSgCbhySy+E55g3Z1QFajiLRpQ5+RsWFDbbI08YEgzyQ0yNCaRvrkgo7kZ1D95rEGRfY96duOQbjzOEqtvYmFChdemZ2+f9Kh/JH1+X9ynxY/zYe/0p/U7WD3QNTYN18loc4aXiB1adXD5Ka2QfNroLudQBmLaJpJB6wASFfuxddsD5yRnO32NSdRaqIWC1x6ti3ZYJZ2RsNwJExPDzjpQTuMOH2jtpu3q7NHmW3snRKy2YAL2UjI0YdeKIlhc/qLCJt9MRcOxWYvujTMD/yGprhG44qf0jjMkJBu7NjuVIMONujabl9b7SUQGfO/t+3rMuC68bQdCGLlO8gf3hvtD99utzXphi6idjC0HKSW/9KzuMkm+syGmIAYq/0L3EFvpZ38uq7z8KzwFFQHI3sBA34bNEr5zpU5OMWg', @@ -94,7 +90,7 @@ describe('config/decrypt/openpgp', () => { }); it('handles PGP org/repo constraint', async () => { - GlobalConfig.set({ privateKey }); + GlobalConfig.set({ privateKey, experimentalFlags: ['useOpenpgp'] }); config.encrypted = { token: 'wcFMAw+4H7SgaqGOAQ//Wp7N0PaDZp0uOdwsc1CuqAq0UPcq+IQdHyKpJs3tHiCecXBHogy4P+rY9nGaUrVneCr4HexuKGuyJf1yl0ZqFffAUac5PjF8eDvjukQGOUq4aBlOogJCEefnuuVxVJx+NRR5iF1P6v57bmI1c+zoqZI/EQB30KU6O1BsdGPLUA/+R3dwCZd5Mbd36s34eYBasqcY9/QbqFcpElXMEPMse3kMCsVXPbZ+UMjtPJiBPUmtJq+ifnu1LzDrfshusSQMwgd/QNk7nEsijiYKllkWhHTP6g7zigvJ46x0h6AYS108YiuK3B9XUhXN9m05Ac6KTEEUdRI3E/dK2dQuRkLjXC8wceQm4A19Gm0uHoMIJYOCbiVoBCH6ayvKbZWZV5lZ4D1JbDNGmKeIj6OX9XWEMKiwTx0Xe89V7BdJzwIGrL0TCLtXuYWZ/R2k+UuBqtgzr44BsBqMpKUA0pcGBoqsEou1M05Ae9fJMF6ADezF5UQZPxT1hrMldiTp3p9iHGfWN2tKHeoW/8CqlIqg9JEkTc+Pl/L9E6ndy5Zjf097PvcmSGhxUQBE7XlrZoIlGhiEU/1HPMen0UUIs0LUu1ywpjCex2yTWnU2YmEwy0MQI1sekSr96QFxDDz9JcynYOYbqR/X9pdxEWyzQ+NJ3n6K97nE1Dj9Sgwu7mFGiUdNkf/SUAF0eZi/eXg71qumpMGBd4eWPtgkeMPLHjvMSYw9vBUfcoKFz6RJ4woG0dw5HOFkPnIjXKWllnl/o01EoBp/o8uswsIS9Nb8i+bp27U6tAHE', @@ -108,7 +104,7 @@ describe('config/decrypt/openpgp', () => { }); it('handles PGP multi-org/repo constraint', async () => { - GlobalConfig.set({ privateKey }); + GlobalConfig.set({ privateKey, experimentalFlags: ['useOpenpgp'] }); config.encrypted = { token: 'wcFMAw+4H7SgaqGOARAAibXL3zr0KZawiND868UGdPpGRo1aVZfn0NUBHpm8mXfgB1rBHaLsP7qa8vxDHpwH9DRD1IyB4vvPUwtu7wmuv1Vtr596tD40CCcCZYB5JjZLWRF0O0xaZFCOi7Z9SqqdaOQoMScyvPO+3/lJkS7zmLllJFH0mQoX5Cr+owUAMSWqbeCQ9r/KAXpnhmpraDjTav48WulcdTMc8iQ/DHimcdzHErLOAjtiQi4OUe1GnDCcN76KQ+c+ZHySnkXrYi/DhOOu9qB4glJ5n68NueFja+8iR39z/wqCI6V6TIUiOyjFN86iVyNPQ4Otem3KuNwrnwSABLDqP491eUNjT8DUDffsyhNC9lnjQLmtViK0EN2yLVpMdHq9cq8lszBChB7gobD9rm8nUHnTuLf6yJvZOj6toD5Yqj8Ibj58wN90Q8CUsBp9/qp0J+hBVUPOx4sT6kM2p6YarlgX3mrIW5c1U+q1eDbCddLjHiU5cW7ja7o+cqlA6mbDRu3HthjBweiXTicXZcRu1o/wy/+laQQ95x5FzAXDnOwQUHBmpTDI3tUJvQ+oy8XyBBbyC0LsBye2c2SLkPJ4Ai3IMR+Mh8puSzVywTbneiAQNBzJHlj5l85nCF2tUjvNo3dWC+9mU5sfXg11iEC6LRbg+icjpqRtTjmQURtciKDUbibWacwU5T/SVAGPXnW7adBOS0PZPIZQcSwjchOdOl0IjzBy6ofu7ODdn2CXZXi8zbevTICXsHvjnW4MAj5oXrStxK3LkWyM3YBOLe7sOfWvWz7n9TM3dHg032navQ', diff --git a/lib/config/experimental-flags.spec.ts b/lib/config/experimental-flags.spec.ts new file mode 100644 index 00000000000000..0504bcc5e9bee8 --- /dev/null +++ b/lib/config/experimental-flags.spec.ts @@ -0,0 +1,33 @@ +import { GlobalConfig } from './global'; + +describe('config/experimental-flags', () => { + describe('experimentalFlagValue()', () => { + beforeEach(() => { + GlobalConfig.reset(); + }); + + it('returns true if flag set', () => { + GlobalConfig.set({ + experimentalFlags: ['disableDockerHubTags'], + }); + expect( + GlobalConfig.getExperimentalFlag('disableDockerHubTags'), + ).toBeTrue(); + }); + + it('returns false if flag not set', () => { + GlobalConfig.set({ + experimentalFlags: ['some-other-flag'], + }); + expect( + GlobalConfig.getExperimentalFlag('disableDockerHubTags'), + ).toBeFalse(); + }); + + it('returns false if experimentalFlags is undefined', () => { + expect( + GlobalConfig.getExperimentalFlag('disableDockerHubTags'), + ).toBeFalse(); + }); + }); +}); diff --git a/lib/config/global.ts b/lib/config/global.ts index 3008fb6117f004..2175444b977012 100644 --- a/lib/config/global.ts +++ b/lib/config/global.ts @@ -33,6 +33,7 @@ export class GlobalConfig { 'gitTimeout', 'platform', 'endpoint', + 'experimentalFlags', 'httpCacheTtlDays', 'autodiscoverRepoSort', 'autodiscoverRepoOrder', @@ -40,6 +41,7 @@ export class GlobalConfig { ]; private static config: RepoGlobalConfig = {}; + private static parsedExperimentalFlags: Set = new Set(); static get(): RepoGlobalConfig; static get( @@ -68,7 +70,28 @@ export class GlobalConfig { return result; } + static getExperimentalFlag(key: string): boolean { + const experimentalFlags = GlobalConfig.get('experimentalFlags'); + + if (!experimentalFlags?.length) { + return false; + } + + if (!GlobalConfig.parsedExperimentalFlags.size) { + for (const flag of experimentalFlags) { + GlobalConfig.parsedExperimentalFlags.add(flag); + } + } + + return GlobalConfig.parsedExperimentalFlags.has(key); + } + + /** + * only used for testing + * @internal + */ static reset(): void { GlobalConfig.config = {}; + GlobalConfig.parsedExperimentalFlags.clear(); } } diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts index baa90a4aa50dbc..ce96ade5f02d37 100644 --- a/lib/config/migration.spec.ts +++ b/lib/config/migration.spec.ts @@ -254,14 +254,14 @@ describe('config/migration', () => { groupName: ['angular packages'], }, ], - }; + } as unknown as RenovateConfig; const { isMigrated, migratedConfig } = configMigration.migrateConfig(config); expect(isMigrated).toBeTrue(); expect(migratedConfig).toEqual({ packageRules: [ { - matchPackagePatterns: '^(@angular|typescript)', + matchPackageNames: ['/^(@angular|typescript)/'], groupName: 'angular packages', }, ], @@ -540,6 +540,7 @@ describe('config/migration', () => { packagePatterns: ['^bar'], excludePackageNames: ['baz'], excludePackagePatterns: ['^baz'], + excludeRepositories: ['abc/def'], sourceUrlPrefixes: ['https://github.com/lodash'], updateTypes: ['major'], }, @@ -551,17 +552,15 @@ describe('config/migration', () => { expect(migratedConfig).toEqual({ packageRules: [ { - excludePackageNames: ['baz'], - excludePackagePatterns: ['^baz'], matchBaseBranches: ['master'], matchDatasources: ['orb'], matchDepTypes: ['peerDependencies'], matchCategories: ['python'], matchManagers: ['dockerfile'], - matchPackageNames: ['foo'], - matchPackagePatterns: ['^bar'], + matchPackageNames: ['foo', '/^bar/', '!baz', '!/^baz/'], + matchRepositories: ['!abc/def'], matchFileNames: ['package.json'], - matchSourceUrlPrefixes: ['https://github.com/lodash'], + matchSourceUrls: ['https://github.com/lodash**'], matchUpdateTypes: ['major'], }, ], @@ -610,7 +609,7 @@ describe('config/migration', () => { packageRules: [ { groupName: 'definitelyTyped', - matchPackagePrefixes: ['@types/'], + matchPackageNames: ['@types/**'], }, { matchDepTypes: ['dependencies'], diff --git a/lib/config/migrations/custom/package-rules-migration.spec.ts b/lib/config/migrations/custom/package-rules-migration.spec.ts index 592e361005aba7..2075a5727420fc 100644 --- a/lib/config/migrations/custom/package-rules-migration.spec.ts +++ b/lib/config/migrations/custom/package-rules-migration.spec.ts @@ -16,8 +16,6 @@ describe('config/migrations/custom/package-rules-migration', () => { depTypeList: [], addLabels: [], packageNames: [], - packagePatterns: [], - sourceUrlPrefixes: [], updateTypes: [], }, ], @@ -107,4 +105,117 @@ describe('config/migrations/custom/package-rules-migration', () => { }, ); }); + + it('should migrate excludePackageNames to matchPackageNames', () => { + expect(PackageRulesMigration).toMigrate( + { + packageRules: [ + { + excludePackageNames: ['foo', 'bar'], + automerge: true, + }, + { + matchPackageNames: ['baz'], + excludePackageNames: ['foo', 'bar'], + automerge: true, + }, + ], + }, + { + packageRules: [ + { + automerge: true, + matchPackageNames: ['!foo', '!bar'], + }, + { + automerge: true, + matchPackageNames: ['baz', '!foo', '!bar'], + }, + ], + }, + ); + }); + + it('should migrate matchPackagePatterns to matchPackageNames', () => { + expect(PackageRulesMigration).toMigrate( + { + packageRules: [ + { + matchPackagePatterns: ['foo', 'bar'], + automerge: true, + }, + { + matchPackageNames: ['baz'], + matchPackagePatterns: ['foo', 'bar'], + automerge: true, + }, + ], + }, + { + packageRules: [ + { + automerge: true, + matchPackageNames: ['/foo/', '/bar/'], + }, + { + automerge: true, + matchPackageNames: ['baz', '/foo/', '/bar/'], + }, + ], + }, + ); + }); + + it('should migrate all match/exclude at once', () => { + expect(PackageRulesMigration).toMigrate( + { + packageRules: [ + { + matchPackagePatterns: ['pattern'], + matchPackagePrefixes: ['prefix1', 'prefix2'], + matchSourceUrlPrefixes: ['prefix1', 'prefix2'], + excludePackageNames: ['excluded'], + excludePackagePatterns: ['excludepattern'], + excludePackagePrefixes: ['prefix1b'], + matchPackageNames: ['mpn1', 'mpn2'], + matchDepPatterns: ['pattern'], + matchDepPrefixes: ['prefix1', 'prefix2'], + excludeDepNames: ['excluded'], + excludeDepPatterns: ['excludepattern'], + excludeDepPrefixes: ['prefix1b'], + matchDepNames: ['mpn1', 'mpn2'], + automerge: true, + }, + ], + }, + { + packageRules: [ + { + matchPackageNames: [ + 'mpn1', + 'mpn2', + '/pattern/', + 'prefix1**', + 'prefix2**', + '!excluded', + '!/excludepattern/', + '!prefix1b**', + ], + matchDepNames: [ + 'mpn1', + 'mpn2', + '/pattern/', + 'prefix1**', + 'prefix2**', + '!excluded', + '!/excludepattern/', + '!prefix1b**', + ], + matchSourceUrls: ['prefix1**', 'prefix2**'], + automerge: true, + }, + ], + }, + ); + }); }); diff --git a/lib/config/migrations/custom/package-rules-migration.ts b/lib/config/migrations/custom/package-rules-migration.ts index 3e6450956a9b70..6b8aea4780ebba 100644 --- a/lib/config/migrations/custom/package-rules-migration.ts +++ b/lib/config/migrations/custom/package-rules-migration.ts @@ -27,6 +27,102 @@ function renameKeys(packageRule: PackageRule): PackageRule { return newPackageRule; } +function mergeMatchers(packageRule: PackageRule): PackageRule { + const newPackageRule: PackageRule = { ...packageRule }; + for (const [key, val] of Object.entries(packageRule)) { + // depName + if (key === 'matchDepPrefixes') { + if (is.array(val, is.string)) { + newPackageRule.matchDepNames ??= []; + newPackageRule.matchDepNames.push(...val.map((v) => `${v}**`)); + } + delete newPackageRule.matchDepPrefixes; + } + if (key === 'matchDepPatterns') { + if (is.array(val, is.string)) { + newPackageRule.matchDepNames ??= []; + newPackageRule.matchDepNames.push(...val.map((v) => `/${v}/`)); + } + delete newPackageRule.matchDepPatterns; + } + if (key === 'excludeDepNames') { + if (is.array(val, is.string)) { + newPackageRule.matchDepNames ??= []; + newPackageRule.matchDepNames.push(...val.map((v) => `!${v}`)); + } + delete newPackageRule.excludeDepNames; + } + if (key === 'excludeDepPrefixes') { + if (is.array(val, is.string)) { + newPackageRule.matchDepNames ??= []; + newPackageRule.matchDepNames.push(...val.map((v) => `!${v}**`)); + } + delete newPackageRule.excludeDepPrefixes; + } + if (key === 'excludeDepPatterns') { + if (is.array(val, is.string)) { + newPackageRule.matchDepNames ??= []; + newPackageRule.matchDepNames.push(...val.map((v) => `!/${v}/`)); + } + delete newPackageRule.excludeDepPatterns; + } + // packageName + if (key === 'matchPackagePrefixes') { + if (is.array(val, is.string)) { + newPackageRule.matchPackageNames ??= []; + newPackageRule.matchPackageNames.push(...val.map((v) => `${v}**`)); + } + delete newPackageRule.matchPackagePrefixes; + } + if (key === 'matchPackagePatterns') { + const patterns = is.string(val) ? [val] : val; + if (is.array(patterns, is.string)) { + newPackageRule.matchPackageNames ??= []; + newPackageRule.matchPackageNames.push(...patterns.map((v) => `/${v}/`)); + } + delete newPackageRule.matchPackagePatterns; + } + if (key === 'excludePackageNames') { + if (is.array(val, is.string)) { + newPackageRule.matchPackageNames ??= []; + newPackageRule.matchPackageNames.push(...val.map((v) => `!${v}`)); + } + delete newPackageRule.excludePackageNames; + } + if (key === 'excludePackagePrefixes') { + if (is.array(val, is.string)) { + newPackageRule.matchPackageNames ??= []; + newPackageRule.matchPackageNames.push(...val.map((v) => `!${v}**`)); + } + delete newPackageRule.excludePackagePrefixes; + } + if (key === 'excludePackagePatterns') { + if (is.array(val, is.string)) { + newPackageRule.matchPackageNames ??= []; + newPackageRule.matchPackageNames.push(...val.map((v) => `!/${v}/`)); + } + delete newPackageRule.excludePackagePatterns; + } + // sourceUrl + if (key === 'matchSourceUrlPrefixes') { + if (is.array(val, is.string)) { + newPackageRule.matchSourceUrls ??= []; + newPackageRule.matchSourceUrls.push(...val.map((v) => `${v}**`)); + } + delete newPackageRule.matchSourceUrlPrefixes; + } + // repository + if (key === 'excludeRepositories') { + if (is.array(val, is.string)) { + newPackageRule.matchRepositories ??= []; + newPackageRule.matchRepositories.push(...val.map((v) => `!${v}`)); + } + delete newPackageRule.excludeRepositories; + } + } + return newPackageRule; +} + export class PackageRulesMigration extends AbstractMigration { override readonly propertyName = 'packageRules'; @@ -34,7 +130,7 @@ export class PackageRulesMigration extends AbstractMigration { let packageRules = this.get('packageRules') as PackageRule[]; if (is.nonEmptyArray(packageRules)) { packageRules = packageRules.map(renameKeys); - + packageRules = packageRules.map(mergeMatchers); this.rewrite(packageRules); } } diff --git a/lib/config/migrations/custom/packages-migration.spec.ts b/lib/config/migrations/custom/packages-migration.spec.ts index 255f9561c777c4..666c8627769fda 100644 --- a/lib/config/migrations/custom/packages-migration.spec.ts +++ b/lib/config/migrations/custom/packages-migration.spec.ts @@ -4,10 +4,10 @@ describe('config/migrations/custom/packages-migration', () => { it('should migrate to package rules', () => { expect(PackagesMigration).toMigrate( { - packages: [{ matchPackagePatterns: ['*'] }], + packages: [{ matchPackageNames: ['*'] }], }, { - packageRules: [{ matchPackagePatterns: ['*'] }], + packageRules: [{ matchPackageNames: ['*'] }], }, ); }); @@ -15,14 +15,11 @@ describe('config/migrations/custom/packages-migration', () => { it('should concat with existing package rules', () => { expect(PackagesMigration).toMigrate( { - packages: [{ matchPackagePatterns: ['*'] }], + packages: [{ matchPackageNames: ['*'] }], packageRules: [{ matchPackageNames: [] }], }, { - packageRules: [ - { matchPackageNames: [] }, - { matchPackagePatterns: ['*'] }, - ], + packageRules: [{ matchPackageNames: [] }, { matchPackageNames: ['*'] }], }, ); }); @@ -30,7 +27,7 @@ describe('config/migrations/custom/packages-migration', () => { it('should ignore non array value', () => { expect(PackagesMigration).toMigrate( { - packages: { matchPackagePatterns: ['*'] }, + packages: { matchPackageNames: ['*'] }, packageRules: [{ matchPackageNames: [] }], }, { diff --git a/lib/config/migrations/custom/platform-commit-migration.spec.ts b/lib/config/migrations/custom/platform-commit-migration.spec.ts new file mode 100644 index 00000000000000..75cd6044af1622 --- /dev/null +++ b/lib/config/migrations/custom/platform-commit-migration.spec.ts @@ -0,0 +1,38 @@ +import { PlatformCommitMigration } from './platform-commit-migration'; + +describe('config/migrations/custom/platform-commit-migration', () => { + it('should migrate platformCommit=true to platformCommit=enabled', () => { + expect(PlatformCommitMigration).toMigrate( + { + // @ts-expect-error: old type + platformCommit: true, + }, + { + platformCommit: 'enabled', + }, + ); + }); + + it('should migrate platformCommit=false to platformCommit=disabled', () => { + expect(PlatformCommitMigration).toMigrate( + { + // @ts-expect-error: old type + platformCommit: false, + }, + { + platformCommit: 'disabled', + }, + ); + }); + + it('should not migrate platformCommit=auto', () => { + expect(PlatformCommitMigration).not.toMigrate( + { + platformCommit: 'auto', + }, + { + platformCommit: 'auto', + }, + ); + }); +}); diff --git a/lib/config/migrations/custom/platform-commit-migration.ts b/lib/config/migrations/custom/platform-commit-migration.ts new file mode 100644 index 00000000000000..3a1fdcebd873f1 --- /dev/null +++ b/lib/config/migrations/custom/platform-commit-migration.ts @@ -0,0 +1,12 @@ +import is from '@sindresorhus/is'; +import { AbstractMigration } from '../base/abstract-migration'; + +export class PlatformCommitMigration extends AbstractMigration { + override readonly propertyName = 'platformCommit'; + + override run(value: unknown): void { + if (is.boolean(value)) { + this.rewrite(value ? 'enabled' : 'disabled'); + } + } +} diff --git a/lib/config/migrations/migrations-service.ts b/lib/config/migrations/migrations-service.ts index 184f4df9ca3ec5..b5c144b210995a 100644 --- a/lib/config/migrations/migrations-service.ts +++ b/lib/config/migrations/migrations-service.ts @@ -38,6 +38,7 @@ import { PackageRulesMigration } from './custom/package-rules-migration'; import { PackagesMigration } from './custom/packages-migration'; import { PathRulesMigration } from './custom/path-rules-migration'; import { PinVersionsMigration } from './custom/pin-versions-migration'; +import { PlatformCommitMigration } from './custom/platform-commit-migration'; import { PostUpdateOptionsMigration } from './custom/post-update-options-migration'; import { RaiseDeprecationWarningsMigration } from './custom/raise-deprecation-warnings-migration'; import { RebaseConflictedPrs } from './custom/rebase-conflicted-prs-migration'; @@ -71,6 +72,7 @@ export class MigrationsService { 'maintainYarnLock', 'statusCheckVerify', 'supportPolicy', + 'transitiveRemediation', 'yarnCacheFolder', 'yarnMaintenanceBranchName', 'yarnMaintenanceCommitMessage', @@ -157,6 +159,7 @@ export class MigrationsService { FetchReleaseNotesMigration, MatchManagersMigration, CustomManagersMigration, + PlatformCommitMigration, ]; static run(originalConfig: RenovateConfig): RenovateConfig { diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index f55e197515e418..751e8a80f5bc57 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -207,8 +207,9 @@ const options: RenovateOptions[] = [ { name: 'onboardingNoDeps', description: 'Onboard the repository even if no dependencies are found.', - type: 'boolean', - default: false, + type: 'string', + default: 'auto', + allowedValues: ['auto', 'enabled', 'disabled'], globalOnly: true, inheritConfigSupport: true, }, @@ -541,21 +542,6 @@ const options: RenovateOptions[] = [ supportedManagers: ['gomod'], }, // Log options - { - name: 'logFile', - description: 'Log file path.', - stage: 'global', - type: 'string', - globalOnly: true, - }, - { - name: 'logFileLevel', - description: 'Set the log file log level.', - stage: 'global', - type: 'string', - default: 'debug', - globalOnly: true, - }, { name: 'logContext', description: 'Add a global or per-repo log context to each log entry.', @@ -1300,19 +1286,6 @@ const options: RenovateOptions[] = [ env: false, patternMatch: true, }, - { - name: 'excludeRepositories', - description: - 'List of repositories to exclude (e.g. `["**/*-archived"]`). Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - }, { name: 'matchBaseBranches', description: @@ -1378,19 +1351,6 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, - { - name: 'excludePackageNames', - description: - 'Package names to exclude. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - }, { name: 'matchDepNames', description: @@ -1404,132 +1364,6 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, - { - name: 'excludeDepNames', - description: - 'Dep names to exclude. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - advancedUse: true, - }, - { - name: 'matchPackagePrefixes', - description: - 'Package name prefixes to match. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - }, - { - name: 'excludePackagePrefixes', - description: - 'Package name prefixes to exclude. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - }, - { - name: 'matchDepPrefixes', - description: - 'Dep names prefixes to match. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - advancedUse: true, - }, - { - name: 'excludeDepPrefixes', - description: - 'Dep names prefixes to exclude. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - advancedUse: true, - }, - { - name: 'matchPackagePatterns', - description: - 'Package name patterns to match. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - format: 'regex', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - }, - { - name: 'excludePackagePatterns', - description: - 'Package name patterns to exclude. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - format: 'regex', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - }, - { - name: 'matchDepPatterns', - description: - 'Dep name patterns to match. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - format: 'regex', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - advancedUse: true, - }, - { - name: 'excludeDepPatterns', - description: - 'Dep name patterns to exclude. Valid only within a `packageRules` object.', - type: 'array', - subType: 'string', - format: 'regex', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - advancedUse: true, - }, { name: 'matchCurrentValue', description: @@ -1580,19 +1414,6 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, - { - name: 'matchSourceUrlPrefixes', - description: - 'A list of source URL prefixes to match against, commonly used to group monorepos or packages from the same organization.', - type: 'array', - subType: 'string', - allowString: true, - stage: 'package', - parents: ['packageRules'], - mergeable: true, - cli: false, - env: false, - }, { name: 'matchSourceUrls', description: 'A list of source URLs to exact match against.', @@ -2133,14 +1954,6 @@ const options: RenovateOptions[] = [ parents: ['customDatasources'], default: [], }, - { - name: 'transitiveRemediation', - description: 'Enable remediation of transitive dependencies.', - type: 'boolean', - default: false, - supportedManagers: ['npm'], - supportedPlatforms: ['github'], - }, { name: 'vulnerabilityAlerts', description: @@ -2153,7 +1966,7 @@ const options: RenovateOptions[] = [ minimumReleaseAge: null, rangeStrategy: 'update-lockfile', commitMessageSuffix: '[SECURITY]', - branchTopic: `{{{datasource}}}-{{{depName}}}-vulnerability`, + branchTopic: `{{{datasource}}}-{{{depNameSanitized}}}-vulnerability`, prCreation: 'immediate', }, mergeable: true, @@ -3045,9 +2858,11 @@ const options: RenovateOptions[] = [ }, { name: 'platformCommit', - description: `Use platform API to perform commits instead of using Git directly.`, - type: 'boolean', - default: false, + description: + 'Use platform API to perform commits instead of using Git directly.', + type: 'string', + default: 'auto', + allowedValues: ['auto', 'disabled', 'enabled'], supportedPlatforms: ['github'], }, { @@ -3110,6 +2925,14 @@ const options: RenovateOptions[] = [ default: null, supportedPlatforms: ['github'], }, + { + name: 'experimentalFlags', + description: 'Set which experimental flags you want Renovate to use.', + type: 'array', + subType: 'string', + default: null, + globalOnly: true, + }, { name: 'httpCacheTtlDays', description: 'Maximum duration in days to keep HTTP cache entries.', diff --git a/lib/config/options/index.ts.orig b/lib/config/options/index.ts.orig new file mode 100644 index 00000000000000..c5d31011950e73 --- /dev/null +++ b/lib/config/options/index.ts.orig @@ -0,0 +1,2994 @@ +import { getManagers } from '../../modules/manager'; +import { getCustomManagers } from '../../modules/manager/custom'; +import { getPlatformList } from '../../modules/platform'; +import { getVersioningList } from '../../modules/versioning'; +import { supportedDatasources } from '../presets/internal/merge-confidence'; +import type { RenovateOptions } from '../types'; + +const options: RenovateOptions[] = [ + { + name: 'mode', + description: 'Mode of operation.', + type: 'string', + default: 'full', + allowedValues: ['full', 'silent'], + }, + { + name: 'allowedHeaders', + description: + 'List of allowed patterns for header names in repository hostRules config.', + type: 'array', + default: ['X-*'], + subType: 'string', + globalOnly: true, + patternMatch: true, + }, + { + name: 'autodiscoverRepoOrder', + description: + 'The order method for autodiscover server side repository search.', + type: 'string', + default: null, + globalOnly: true, + allowedValues: ['asc', 'desc'], + supportedPlatforms: ['gitea'], + }, + { + name: 'autodiscoverRepoSort', + description: + 'The sort method for autodiscover server side repository search.', + type: 'string', + default: null, + globalOnly: true, + allowedValues: ['alpha', 'created', 'updated', 'size', 'id'], + supportedPlatforms: ['gitea'], + }, + { + name: 'allowedEnv', + description: + 'List of allowed patterns for environment variable names in repository env config.', + type: 'array', + default: [], + subType: 'string', + globalOnly: true, + patternMatch: true, + }, + { + name: 'detectGlobalManagerConfig', + description: + 'If `true`, Renovate tries to detect global manager configuration from the file system.', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'detectHostRulesFromEnv', + description: + 'If `true`, Renovate tries to detect host rules from environment variables.', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'mergeConfidenceEndpoint', + description: + 'If set, Renovate will query this API for Merge Confidence data.', + stage: 'global', + type: 'string', + default: 'https://developer.mend.io/', + advancedUse: true, + globalOnly: true, + }, + { + name: 'mergeConfidenceDatasources', + description: + 'If set, Renovate will query the merge-confidence JSON API only for datasources that are part of this list.', + stage: 'global', + allowedValues: supportedDatasources, + default: supportedDatasources, + type: 'array', + subType: 'string', + globalOnly: true, + }, + { + name: 'useCloudMetadataServices', + description: + 'If `false`, Renovate does not try to access cloud metadata services.', + type: 'boolean', + default: true, + globalOnly: true, + }, + { + name: 'userAgent', + description: + 'If set to any string, Renovate will use this as the `user-agent` it sends with HTTP requests.', + type: 'string', + default: null, + globalOnly: true, + }, + { + name: 'allowPostUpgradeCommandTemplating', + description: + 'Set this to `false` to disable template compilation for post-upgrade commands.', + type: 'boolean', + default: true, + globalOnly: true, + }, + { + name: 'allowedPostUpgradeCommands', + description: + 'A list of regular expressions that decide which post-upgrade tasks are allowed.', + type: 'array', + subType: 'string', + default: [], + globalOnly: true, + }, + { + name: 'postUpgradeTasks', + description: + 'Post-upgrade tasks that are executed before a commit is made by Renovate.', + type: 'object', + default: { + commands: [], + fileFilters: [], + executionMode: 'update', + }, + }, + { + name: 'commands', + description: + 'A list of post-upgrade commands that are executed before a commit is made by Renovate.', + type: 'array', + subType: 'string', + parents: ['postUpgradeTasks'], + default: [], + cli: false, + }, + { + name: 'fileFilters', + description: + 'Files that match the glob pattern will be committed after running a post-upgrade task.', + type: 'array', + subType: 'string', + parents: ['postUpgradeTasks'], + default: ['**/*'], + cli: false, + }, + { + name: 'format', + description: 'Format of the custom datasource.', + type: 'string', + parents: ['customDatasources'], + default: 'json', + allowedValues: ['json', 'plain'], + cli: false, + env: false, + }, + { + name: 'executionMode', + description: + 'Controls when the post upgrade tasks run: on every update, or once per upgrade branch.', + type: 'string', + parents: ['postUpgradeTasks'], + allowedValues: ['update', 'branch'], + default: 'update', + cli: false, + }, + { + name: 'onboardingBranch', + description: + 'Change this value to override the default onboarding branch name.', + type: 'string', + default: 'renovate/configure', + globalOnly: true, + inheritConfigSupport: true, + cli: false, + }, + { + name: 'onboardingCommitMessage', + description: + 'Change this value to override the default onboarding commit message.', + type: 'string', + default: null, + globalOnly: true, + inheritConfigSupport: true, + cli: false, + }, + { + name: 'onboardingConfigFileName', + description: + 'Change this value to override the default onboarding config file name.', + type: 'string', + default: 'renovate.json', + globalOnly: true, + inheritConfigSupport: true, + cli: false, + }, + { + name: 'onboardingNoDeps', + description: 'Onboard the repository even if no dependencies are found.', + type: 'string', + default: 'auto', + allowedValues: ['auto', 'enabled', 'disabled'], + globalOnly: true, + inheritConfigSupport: true, + }, + { + name: 'onboardingPrTitle', + description: + 'Change this value to override the default onboarding PR title.', + type: 'string', + default: 'Configure Renovate', + globalOnly: true, + inheritConfigSupport: true, + cli: false, + }, + { + name: 'configMigration', + description: 'Enable this to get config migration PRs when needed.', + stage: 'repository', + type: 'boolean', + default: false, + experimental: true, + experimentalDescription: + 'Config migration PRs are still being improved, in particular to reduce the amount of reordering and whitespace changes.', + experimentalIssues: [16359], + }, + { + name: 'productLinks', + description: 'Links which are used in PRs, issues and comments.', + type: 'object', + globalOnly: true, + mergeable: true, + default: { + documentation: 'https://docs.renovatebot.com/', + help: 'https://github.com/renovatebot/renovate/discussions', + homepage: 'https://github.com/renovatebot/renovate', + }, + additionalProperties: { + type: 'string', + format: 'uri', + }, + }, + { + name: 'secrets', + description: 'Object which holds secret name/value pairs.', + type: 'object', + globalOnly: true, + mergeable: true, + default: {}, + additionalProperties: { + type: 'string', + }, + }, + { + name: 'statusCheckNames', + description: 'Custom strings to use as status check names.', + type: 'object', + mergeable: true, + advancedUse: true, + default: { + artifactError: 'renovate/artifacts', + configValidation: 'renovate/config-validation', + mergeConfidence: 'renovate/merge-confidence', + minimumReleaseAge: 'renovate/stability-days', + }, + }, + { + name: 'extends', + description: 'Configuration presets to use or extend.', + stage: 'package', + type: 'array', + subType: 'string', + allowString: true, + cli: false, + }, + { + name: 'ignorePresets', + description: + 'A list of presets to ignore, including any that are nested inside an `extends` array.', + stage: 'package', + type: 'array', + subType: 'string', + allowString: true, + cli: false, + }, + { + name: 'migratePresets', + description: + 'Define presets here which have been removed or renamed and should be migrated automatically.', + type: 'object', + globalOnly: true, + default: {}, + additionalProperties: { + type: 'string', + }, + }, + { + name: 'presetCachePersistence', + description: 'Cache resolved presets in package cache.', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'globalExtends', + description: + 'Configuration presets to use or extend for a self-hosted config.', + type: 'array', + subType: 'string', + globalOnly: true, + }, + { + name: 'description', + description: 'Plain text description for a config or preset.', + type: 'array', + subType: 'string', + stage: 'repository', + allowString: true, + mergeable: true, + cli: false, + env: false, + }, + { + name: 'enabled', + description: `Enable or disable Renovate bot.`, + stage: 'package', + type: 'boolean', + default: true, + }, + { + name: 'constraintsFiltering', + description: 'Perform release filtering based on language constraints.', + type: 'string', + allowedValues: ['none', 'strict'], + cli: false, + default: 'none', + }, + { + name: 'repositoryCache', + description: + 'This option decides if Renovate uses a JSON cache to speed up extractions.', + globalOnly: true, + type: 'string', + allowedValues: ['disabled', 'enabled', 'reset'], + stage: 'repository', + default: 'disabled', + }, + { + name: 'repositoryCacheType', + description: + 'Set the type of renovate repository cache if `repositoryCache` is enabled.', + globalOnly: true, + type: 'string', + stage: 'repository', + default: 'local', + }, + { + name: 'reportType', + description: 'Set how, or if, reports should be generated.', + globalOnly: true, + type: 'string', + default: null, + experimental: true, + allowedValues: ['logging', 'file', 's3'], + }, + { + name: 'reportPath', + description: + 'Path to where the file should be written. In case of `s3` this has to be a full S3 URI.', + globalOnly: true, + type: 'string', + default: null, + experimental: true, + }, + { + name: 'force', + description: + 'Any configuration set in this object will force override existing settings.', + stage: 'package', + globalOnly: true, + type: 'object', + cli: false, + mergeable: true, + }, + { + name: 'forceCli', + description: + 'Decides if CLI configuration options are moved to the `force` config section.', + stage: 'global', + type: 'boolean', + default: true, + globalOnly: true, + }, + { + name: 'draftPR', + description: + 'If set to `true` then Renovate creates draft PRs, instead of normal status PRs.', + type: 'boolean', + default: false, + supportedPlatforms: ['azure', 'gitea', 'github', 'gitlab'], + }, + { + name: 'dryRun', + description: + 'If enabled, perform a dry run by logging messages instead of creating/updating/deleting branches and PRs.', + type: 'string', + globalOnly: true, + allowedValues: ['extract', 'lookup', 'full'], + default: null, + }, + { + name: 'printConfig', + description: + 'If enabled, Renovate logs the fully resolved config for each repository, plus the fully resolved presets.', + type: 'boolean', + default: false, + }, + { + name: 'binarySource', + description: + 'Controls how third-party tools like npm or Gradle are called: directly, via Docker sidecar containers, or via dynamic install.', + globalOnly: true, + type: 'string', + allowedValues: ['global', 'docker', 'install', 'hermit'], + default: 'install', + }, + { + name: 'redisUrl', + description: + 'If set, this Redis URL will be used for caching instead of the file system.', + stage: 'global', + type: 'string', + globalOnly: true, + }, + { + name: 'redisPrefix', + description: 'Key prefix for redis cache entries.', + stage: 'global', + type: 'string', + globalOnly: true, + }, + { + name: 'baseDir', + description: + 'The base directory for Renovate to store local files, including repository files and cache. If left empty, Renovate will create its own temporary directory to use.', + stage: 'global', + type: 'string', + globalOnly: true, + }, + { + name: 'cacheDir', + description: + 'The directory where Renovate stores its cache. If left empty, Renovate creates a subdirectory within the `baseDir`.', + globalOnly: true, + type: 'string', + }, + { + name: 'containerbaseDir', + description: + 'The directory where Renovate stores its containerbase cache. If left empty, Renovate creates a subdirectory within the `cacheDir`.', + globalOnly: true, + type: 'string', + }, + { + name: 'customEnvVariables', + description: + 'Custom environment variables for child processes and sidecar Docker containers.', + globalOnly: true, + type: 'object', + default: {}, + }, + { + name: 'env', + description: + 'Environment variables that Renovate uses when executing package manager commands.', + type: 'object', + default: {}, + }, + { + name: 'customDatasources', + description: 'Defines custom datasources for usage by managers.', + type: 'object', + experimental: true, + experimentalIssues: [23286], + default: {}, + mergeable: true, + }, + { + name: 'dockerChildPrefix', + description: + 'Change this value to add a prefix to the Renovate Docker sidecar container names and labels.', + type: 'string', + globalOnly: true, + default: 'renovate_', + }, + { + name: 'dockerCliOptions', + description: + 'Pass CLI flags to `docker run` command when `binarySource=docker`.', + type: 'string', + globalOnly: true, + }, + { + name: 'dockerSidecarImage', + description: + 'Change this value to override the default Renovate sidecar image.', + type: 'string', + default: 'ghcr.io/containerbase/sidecar:10.11.1', + globalOnly: true, + }, + { + name: 'dockerUser', + description: + 'Set the `UID` and `GID` for Docker-based binaries if you use `binarySource=docker`.', + globalOnly: true, + type: 'string', + }, + { + name: 'composerIgnorePlatformReqs', + description: + 'Configure use of `--ignore-platform-reqs` or `--ignore-platform-req` for the Composer package manager.', + type: 'array', + subType: 'string', + default: [], + }, + { + name: 'goGetDirs', + description: 'Directory pattern to run `go get` on.', + type: 'array', + subType: 'string', + default: ['./...'], + supportedManagers: ['gomod'], + }, + // Log options + { + name: 'logFile', + description: 'Log file path.', + stage: 'global', + type: 'string', + globalOnly: true, + deprecationMsg: + 'Instead of configuring log file path in the file config. Use the `LOG_FILE` environment variable instead.', + }, + { + name: 'logFileLevel', + description: 'Set the log file log level.', + stage: 'global', + type: 'string', + default: 'debug', + globalOnly: true, + deprecationMsg: + 'Instead of configuring log file level in the file config. Use the `LOG_FILE_LEVEL` environment variable instead.', + }, + { + name: 'logContext', + description: 'Add a global or per-repo log context to each log entry.', + globalOnly: true, + type: 'string', + default: null, + stage: 'global', + }, + // Onboarding + { + name: 'onboarding', + description: 'Require a Configuration PR first.', + stage: 'repository', + type: 'boolean', + globalOnly: true, + inheritConfigSupport: true, + }, + { + name: 'onboardingConfig', + description: 'Configuration to use for onboarding PRs.', + stage: 'repository', + type: 'object', + default: { $schema: 'https://docs.renovatebot.com/renovate-schema.json' }, + globalOnly: true, + inheritConfigSupport: true, + mergeable: true, + }, + { + name: 'onboardingRebaseCheckbox', + description: + 'Set to enable rebase/retry markdown checkbox for onboarding PRs.', + type: 'boolean', + default: false, + supportedPlatforms: ['gitea', 'github', 'gitlab'], + globalOnly: true, + experimental: true, + experimentalIssues: [17633], + }, + { + name: 'forkProcessing', + description: + 'Whether to process forked repositories. By default, all forked repositories are skipped when in `autodiscover` mode.', + stage: 'repository', + type: 'string', + allowedValues: ['auto', 'enabled', 'disabled'], + default: 'auto', + }, + { + name: 'includeMirrors', + description: + 'Whether to process repositories that are mirrors. By default, repositories that are mirrors are skipped.', + type: 'boolean', + default: false, + supportedPlatforms: ['gitlab'], + globalOnly: true, + }, + { + name: 'forkCreation', + description: + 'Whether to create forks as needed at runtime when running in "fork mode".', + stage: 'repository', + type: 'boolean', + globalOnly: true, + supportedPlatforms: ['github'], + experimental: true, + default: true, + }, + { + name: 'forkToken', + description: 'Set a personal access token here to enable "fork mode".', + stage: 'repository', + type: 'string', + globalOnly: true, + supportedPlatforms: ['github'], + experimental: true, + }, + { + name: 'forkOrg', + description: + 'The preferred organization to create or find forked repositories, when in fork mode.', + stage: 'repository', + type: 'string', + globalOnly: true, + supportedPlatforms: ['github'], + experimental: true, + }, + { + name: 'githubTokenWarn', + description: 'Display warnings about GitHub token not being set.', + type: 'boolean', + default: true, + globalOnly: true, + }, + { + name: 'encryptedWarning', + description: 'Warning text to use if encrypted config is found.', + type: 'string', + globalOnly: true, + advancedUse: true, + }, + { + name: 'inheritConfig', + description: + 'If `true`, Renovate will inherit configuration from the `inheritConfigFileName` file in `inheritConfigRepoName`.', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'inheritConfigRepoName', + description: + 'Renovate will look in this repo for the `inheritConfigFileName`.', + type: 'string', + default: '{{parentOrg}}/renovate-config', + globalOnly: true, + }, + { + name: 'inheritConfigFileName', + description: + 'Renovate will look for this config file name in the `inheritConfigRepoName`.', + type: 'string', + default: 'org-inherited-config.json', + globalOnly: true, + }, + { + name: 'inheritConfigStrict', + description: + 'If `true`, any `inheritedConfig` fetch error will result in an aborted run.', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'requireConfig', + description: + "Controls Renovate's behavior regarding repository config files such as `renovate.json`.", + stage: 'repository', + type: 'string', + default: 'required', + allowedValues: ['required', 'optional', 'ignored'], + globalOnly: true, + inheritConfigSupport: true, + }, + { + name: 'optimizeForDisabled', + description: + 'Set to `true` to perform a check for disabled config prior to cloning.', + stage: 'repository', + type: 'boolean', + default: false, + globalOnly: true, + }, + // Dependency Dashboard + { + name: 'dependencyDashboard', + description: + 'Whether to create a "Dependency Dashboard" issue in the repository.', + type: 'boolean', + default: false, + }, + { + name: 'dependencyDashboardApproval', + description: + 'Controls if updates need manual approval from the Dependency Dashboard issue before PRs are created.', + type: 'boolean', + default: false, + }, + { + name: 'dependencyDashboardAutoclose', + description: + 'Set to `true` to let Renovate close the Dependency Dashboard issue if there are no more updates.', + type: 'boolean', + default: false, + }, + { + name: 'dependencyDashboardTitle', + description: 'Title for the Dependency Dashboard issue.', + type: 'string', + default: `Dependency Dashboard`, + }, + { + name: 'dependencyDashboardHeader', + description: + 'Any text added here will be placed first in the Dependency Dashboard issue body.', + type: 'string', + default: + 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more.', + }, + { + name: 'dependencyDashboardFooter', + description: + 'Any text added here will be placed last in the Dependency Dashboard issue body, with a divider separator before it.', + type: 'string', + }, + { + name: 'dependencyDashboardLabels', + description: + 'These labels will always be applied on the Dependency Dashboard issue, even when they have been removed manually.', + type: 'array', + subType: 'string', + default: null, + }, + { + name: 'dependencyDashboardOSVVulnerabilitySummary', + description: + 'Control if the Dependency Dashboard issue lists CVEs supplied by [osv.dev](https://osv.dev).', + type: 'string', + allowedValues: ['none', 'all', 'unresolved'], + default: 'none', + experimental: true, + }, + { + name: 'configWarningReuseIssue', + description: + 'Set this to `false` to make Renovate create a new issue for each config warning, instead of reopening or reusing an existing issue.', + type: 'boolean', + default: true, + }, + + // encryption + { + name: 'privateKey', + description: 'Server-side private key.', + stage: 'repository', + type: 'string', + replaceLineReturns: true, + globalOnly: true, + }, + { + name: 'privateKeyOld', + description: 'Secondary or old private key to try.', + stage: 'repository', + type: 'string', + replaceLineReturns: true, + globalOnly: true, + }, + { + name: 'privateKeyPath', + description: 'Path to the Server-side private key.', + stage: 'repository', + type: 'string', + globalOnly: true, + }, + { + name: 'privateKeyPathOld', + description: 'Path to the Server-side old private key.', + stage: 'repository', + type: 'string', + globalOnly: true, + }, + { + name: 'encrypted', + description: + 'An object containing configuration encrypted with project key.', + stage: 'repository', + type: 'object', + default: null, + }, + // Scheduling + { + name: 'timezone', + description: + 'Must conform to [IANA Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) format.', + type: 'string', + }, + { + name: 'schedule', + description: 'Limit branch creation to these times of day or week.', + type: 'array', + subType: 'string', + allowString: true, + cli: true, + env: false, + default: ['at any time'], + }, + { + name: 'automergeSchedule', + description: 'Limit automerge to these times of day or week.', + type: 'array', + subType: 'string', + allowString: true, + cli: true, + env: false, + default: ['at any time'], + }, + { + name: 'updateNotScheduled', + description: + 'Whether to update branches when not scheduled. Renovate will not create branches outside of the schedule.', + stage: 'branch', + type: 'boolean', + default: true, + }, + // Bot administration + { + name: 'persistRepoData', + description: + 'If set to `true`: keep repository data between runs instead of deleting the data.', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'exposeAllEnv', + description: + 'Set this to `true` to allow passing of all environment variables to package managers.', + globalOnly: true, + type: 'boolean', + default: false, + }, + { + name: 'allowPlugins', + description: + 'Set this to `true` if repositories are allowed to run install plugins.', + globalOnly: true, + type: 'boolean', + default: false, + }, + { + name: 'allowScripts', + description: + 'Set this to `true` if repositories are allowed to run install scripts.', + globalOnly: true, + type: 'boolean', + default: false, + }, + { + name: 'allowCustomCrateRegistries', + description: 'Set this to `true` to allow custom crate registries.', + globalOnly: true, + type: 'boolean', + default: false, + }, + { + name: 'ignorePlugins', + description: + 'Set this to `true` if `allowPlugins=true` but you wish to skip running plugins when updating lock files.', + type: 'boolean', + default: false, + }, + { + name: 'ignoreScripts', + description: + 'Set this to `false` if `allowScripts=true` and you wish to run scripts when updating lock files.', + type: 'boolean', + default: true, + supportedManagers: ['npm', 'composer'], + }, + { + name: 'platform', + description: 'Platform type of repository.', + type: 'string', + allowedValues: getPlatformList(), + default: 'github', + globalOnly: true, + }, + { + name: 'endpoint', + description: 'Custom endpoint to use.', + type: 'string', + globalOnly: true, + default: null, + }, + { + name: 'token', + description: 'Repository Auth Token.', + stage: 'repository', + type: 'string', + globalOnly: true, + }, + { + name: 'username', + description: 'Username for authentication.', + stage: 'repository', + type: 'string', + supportedPlatforms: ['azure', 'bitbucket', 'bitbucket-server'], + globalOnly: true, + }, + { + name: 'password', + description: 'Password for authentication.', + stage: 'repository', + type: 'string', + supportedPlatforms: ['azure', 'bitbucket', 'bitbucket-server'], + globalOnly: true, + }, + { + name: 'npmrc', + description: + 'String copy of `.npmrc` file. Use `\\n` instead of line breaks.', + stage: 'branch', + type: 'string', + }, + { + name: 'npmrcMerge', + description: + 'Whether to merge `config.npmrc` with repo `.npmrc` content if both are found.', + stage: 'branch', + type: 'boolean', + default: false, + }, + { + name: 'npmToken', + description: 'npm token used to authenticate with the default registry.', + stage: 'branch', + type: 'string', + }, + { + name: 'updateLockFiles', + description: 'Set to `false` to disable lock file updating.', + type: 'boolean', + default: true, + supportedManagers: ['npm'], + }, + { + name: 'skipInstalls', + description: + 'Skip installing modules/dependencies if lock file updating is possible without a full install.', + type: 'boolean', + default: null, + }, + { + name: 'autodiscover', + description: 'Autodiscover all repositories.', + stage: 'global', + type: 'boolean', + default: false, + globalOnly: true, + }, + { + name: 'autodiscoverFilter', + description: 'Filter the list of autodiscovered repositories.', + stage: 'global', + type: 'array', + subType: 'string', + allowString: true, + default: null, + globalOnly: true, + }, + { + name: 'autodiscoverNamespaces', + description: + 'Filter the list of autodiscovered repositories by namespaces.', + stage: 'global', + type: 'array', + subType: 'string', + default: null, + globalOnly: true, + supportedPlatforms: ['gitea', 'gitlab'], + }, + { + name: 'autodiscoverProjects', + description: + 'Filter the list of autodiscovered repositories by project names.', + stage: 'global', + type: 'array', + subType: 'string', + default: null, + globalOnly: true, + supportedPlatforms: ['bitbucket'], + patternMatch: true, + }, + { + name: 'autodiscoverTopics', + description: 'Filter the list of autodiscovered repositories by topics.', + stage: 'global', + type: 'array', + subType: 'string', + default: null, + globalOnly: true, + supportedPlatforms: ['gitea', 'github', 'gitlab'], + }, + { + name: 'prCommitsPerRunLimit', + description: + 'Set the maximum number of commits per Renovate run. By default there is no limit.', + stage: 'global', + type: 'integer', + default: 0, + globalOnly: true, + }, + { + name: 'repositories', + description: 'List of Repositories.', + stage: 'global', + type: 'array', + subType: 'string', + cli: false, + globalOnly: true, + }, + { + name: 'baseBranches', + description: + 'List of one or more custom base branches defined as exact strings and/or via regex expressions.', + type: 'array', + subType: 'string', + stage: 'package', + cli: false, + }, + { + name: 'useBaseBranchConfig', + description: + 'Whether to read configuration from `baseBranches` instead of only the default branch.', + type: 'string', + allowedValues: ['merge', 'none'], + default: 'none', + }, + { + name: 'gitAuthor', + description: + 'Author to use for Git commits. Must conform to [RFC5322](https://datatracker.ietf.org/doc/html/rfc5322).', + type: 'string', + }, + { + name: 'gitPrivateKey', + description: 'PGP key to use for signing Git commits.', + type: 'string', + cli: false, + globalOnly: true, + stage: 'global', + }, + { + name: 'gitIgnoredAuthors', + description: + 'Git authors which are ignored by Renovate. Must conform to [RFC5322](https://datatracker.ietf.org/doc/html/rfc5322).', + type: 'array', + subType: 'string', + stage: 'repository', + }, + { + name: 'gitTimeout', + description: + 'Configure the timeout with a number of milliseconds to wait for a Git task.', + type: 'integer', + globalOnly: true, + default: 0, + }, + { + name: 'enabledManagers', + description: + 'A list of package managers to enable. Only managers on the list are enabled.', + type: 'array', + subType: 'string', + mergeable: false, + stage: 'repository', + }, + { + name: 'includePaths', + description: 'Include package files only within these defined paths.', + type: 'array', + subType: 'string', + stage: 'repository', + default: [], + }, + { + name: 'ignorePaths', + description: + 'Skip any package file whose path matches one of these. Can be a string or glob pattern.', + type: 'array', + mergeable: false, + subType: 'string', + stage: 'repository', + default: ['**/node_modules/**', '**/bower_components/**'], + }, + { + name: 'excludeCommitPaths', + description: + 'A file matching any of these glob patterns will not be committed, even if the file has been updated.', + type: 'array', + subType: 'string', + default: [], + advancedUse: true, + }, + { + name: 'executionTimeout', + description: + 'Default execution timeout in minutes for child processes Renovate creates.', + type: 'integer', + default: 15, + globalOnly: true, + }, + { + name: 'registryAliases', + description: 'Aliases for registries.', + mergeable: true, + type: 'object', + default: {}, + additionalProperties: { + type: 'string', + }, + supportedManagers: [ + 'ansible', + 'bitbucket-pipelines', + 'crossplane', + 'devcontainer', + 'docker-compose', + 'dockerfile', + 'droneci', + 'gitlabci', + 'helm-requirements', + 'helmfile', + 'helmv3', + 'kubernetes', + 'kustomize', + 'terraform', + 'vendir', + 'woodpecker', + ], + }, + { + name: 'defaultRegistryUrls', + description: + 'List of registry URLs to use as the default for a datasource.', + type: 'array', + subType: 'string', + default: null, + stage: 'branch', + cli: false, + env: false, + }, + { + name: 'defaultRegistryUrlTemplate', + description: + 'Template for generating a `defaultRegistryUrl` for custom datasource.', + type: 'string', + default: '', + parents: ['customDatasources'], + cli: false, + env: false, + }, + { + name: 'registryUrls', + description: + 'List of URLs to try for dependency lookup. Package manager specific.', + type: 'array', + subType: 'string', + default: null, + stage: 'branch', + cli: false, + env: false, + }, + { + name: 'extractVersion', + description: + "A regex (`re2`) to extract a version from a datasource's raw version string.", + type: 'string', + format: 'regex', + cli: false, + env: false, + }, + { + name: 'versionCompatibility', + description: + 'A regex (`re2`) with named capture groups to show how version and compatibility are split from a raw version string.', + type: 'string', + format: 'regex', + cli: false, + env: false, + }, + { + name: 'versioning', + description: 'Versioning to use for filtering and comparisons.', + type: 'string', + allowedValues: getVersioningList(), + cli: false, + env: false, + }, + { + name: 'azureWorkItemId', + description: + 'The id of an existing work item on Azure Boards to link to each PR.', + type: 'integer', + default: 0, + supportedPlatforms: ['azure'], + }, + { + name: 'autoApprove', + description: 'Set to `true` to automatically approve PRs.', + type: 'boolean', + default: false, + supportedPlatforms: ['azure', 'gerrit', 'gitlab'], + }, + // depType + { + name: 'ignoreDeps', + description: 'Dependencies to ignore.', + type: 'array', + subType: 'string', + stage: 'package', + mergeable: true, + }, + { + name: 'updateInternalDeps', + description: + 'Whether to update internal dep versions in a monorepo. Works on Yarn Workspaces.', + type: 'boolean', + default: false, + stage: 'package', + }, + { + name: 'packageRules', + description: 'Rules for matching packages.', + type: 'array', + stage: 'package', + mergeable: true, + }, + { + name: 'matchCurrentAge', + description: + 'Matches the current age of the package derived from its release timestamp. Valid only within a `packageRules` object.', + type: 'string', + parents: ['packageRules'], + stage: 'package', + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchCategories', + description: + 'List of categories to match (for example: `["python"]`). Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + parents: ['packageRules'], + stage: 'package', + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchRepositories', + description: + 'List of repositories to match (e.g. `["**/*-archived"]`). Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + patternMatch: true, + }, + { + name: 'matchBaseBranches', + description: + 'List of strings containing exact matches (e.g. `["main"]`) and/or regex expressions (e.g. `["/^release/.*/"]`). Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + parents: ['packageRules'], + stage: 'package', + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchManagers', + description: + 'List of package managers to match (e.g. `["pipenv"]`). Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + parents: ['packageRules'], + stage: 'package', + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchDatasources', + description: + 'List of datasources to match (e.g. `["orb"]`). Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + parents: ['packageRules'], + stage: 'package', + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchDepTypes', + description: + 'List of depTypes to match (e.g. [`peerDependencies`]). Valid only within `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + parents: ['packageRules'], + stage: 'package', + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchPackageNames', + description: + 'Package names to match. Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchDepNames', + description: + 'Dep names to match. Valid only within a `packageRules` object.', + type: 'array', + subType: 'string', + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchCurrentValue', + description: + 'A regex or glob pattern to match against the raw `currentValue` string of a dependency. Valid only within a `packageRules` object.', + type: 'string', + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchCurrentVersion', + description: + 'A version, or range of versions, to match against the current version of a package. Valid only within a `packageRules` object.', + type: 'string', + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchNewValue', + description: + 'A regex or glob pattern to match against the raw `newValue` string of a dependency. Valid only within a `packageRules` object.', + type: 'string', + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { +<<<<<<< HEAD + name: 'sourceUrl', + description: 'The source URL of the package.', + type: 'string', + parents: ['packageRules'], + cli: false, + env: false, + }, + { + name: 'sourceDirectory', + description: + 'The source directory in which the package is present at its source.', + type: 'string', + parents: ['packageRules'], + cli: false, + env: false, + }, + { + name: 'matchSourceUrlPrefixes', + description: + 'A list of source URL prefixes to match against, commonly used to group monorepos or packages from the same organization.', + type: 'array', + subType: 'string', + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { +======= +>>>>>>> b66597a89 (feat(packageRules): migrate matchers and excludes (#28602)) + name: 'matchSourceUrls', + description: 'A list of source URLs to exact match against.', + type: 'array', + subType: 'string', + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { + name: 'autoReplaceGlobalMatch', + description: + 'Control whether replacement regular expressions are global matches or only the first match.', + type: 'boolean', + default: true, + }, + { + name: 'replacementName', + description: + 'The name of the new dependency that replaces the old deprecated dependency.', + type: 'string', + stage: 'package', + parents: ['packageRules'], + cli: false, + env: false, + }, + { + name: 'replacementNameTemplate', + description: 'Controls what the replacement package name.', + type: 'string', + default: '{{{packageName}}}', + stage: 'package', + parents: ['packageRules'], + cli: false, + env: false, + }, + { + name: 'replacementVersion', + description: + 'The version of the new dependency that replaces the old deprecated dependency.', + type: 'string', + stage: 'package', + parents: ['packageRules'], + cli: false, + env: false, + }, + { + name: 'matchConfidence', + description: + 'Merge confidence levels to match against (`low`, `neutral`, `high`, `very high`). Valid only within `packageRules` object.', + type: 'array', + subType: 'string', + allowedValues: ['low', 'neutral', 'high', 'very high'], + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchUpdateTypes', + description: + 'Update types to match against (`major`, `minor`, `pin`, `pinDigest`, etc). Valid only within `packageRules` object.', + type: 'array', + subType: 'string', + allowedValues: [ + 'major', + 'minor', + 'patch', + 'pin', + 'pinDigest', + 'digest', + 'lockFileMaintenance', + 'rollback', + 'bump', + 'replacement', + ], + allowString: true, + stage: 'package', + parents: ['packageRules'], + mergeable: true, + cli: false, + env: false, + }, + { + name: 'matchFileNames', + description: + 'List of strings to do an exact match against package and lock files with full path. Only works inside a `packageRules` object.', + type: 'array', + subType: 'string', + stage: 'repository', + parents: ['packageRules'], + cli: false, + env: false, + }, + // Version behavior + { + name: 'allowedVersions', + description: + 'A version range or regex pattern capturing allowed versions for dependencies.', + type: 'string', + parents: ['packageRules'], + stage: 'package', + cli: false, + env: false, + }, + { + name: 'changelogUrl', + description: + 'If set, Renovate will use this URL to fetch changelogs for a matched dependency. Valid only within a `packageRules` object.', + type: 'string', + stage: 'pr', + parents: ['packageRules'], + cli: false, + env: false, + }, + { + name: 'pinDigests', + description: 'Whether to add digests to Dockerfile source images.', + type: 'boolean', + default: false, + }, + { + name: 'separateMajorMinor', + description: + 'If set to `false`, Renovate will upgrade dependencies to their latest release only. Renovate will not separate major or minor branches.', + type: 'boolean', + default: true, + }, + { + name: 'separateMultipleMajor', + description: + 'If set to `true`, PRs will be raised separately for each available `major` upgrade version.', + stage: 'package', + type: 'boolean', + default: false, + }, + { + name: 'separateMultipleMinor', + description: + 'If set to `true`, Renovate creates separate PRs for each `minor` stream.', + stage: 'package', + type: 'boolean', + default: false, + experimental: true, + }, + { + name: 'separateMinorPatch', + description: + 'If set to `true`, Renovate will separate `minor` and `patch` updates into separate branches.', + type: 'boolean', + default: false, + }, + { + name: 'ignoreUnstable', + description: 'Ignore versions with unstable SemVer.', + stage: 'package', + type: 'boolean', + default: true, + }, + { + name: 'ignoreDeprecated', + description: + 'Avoid upgrading from a non-deprecated version to a deprecated one.', + stage: 'package', + type: 'boolean', + default: true, + }, + { + name: 'followTag', + description: 'If defined, packages will follow this release tag exactly.', + stage: 'package', + type: 'string', + cli: false, + env: false, + advancedUse: true, + }, + { + name: 'respectLatest', + description: 'Ignore versions newer than npm "latest" version.', + stage: 'package', + type: 'boolean', + default: true, + }, + { + name: 'rangeStrategy', + description: 'Determines how to modify or update existing ranges.', + type: 'string', + default: 'auto', + allowedValues: [ + 'auto', + 'pin', + 'bump', + 'replace', + 'widen', + 'update-lockfile', + 'in-range-only', + ], + cli: false, + env: false, + }, + { + name: 'branchPrefix', + description: 'Prefix to use for all branch names.', + stage: 'branch', + type: 'string', + default: `renovate/`, + }, + { + name: 'branchPrefixOld', + description: 'Old branchPrefix value to check for existing PRs.', + stage: 'branch', + type: 'string', + default: `renovate/`, + }, + { + name: 'bumpVersion', + description: 'Bump the version in the package file being updated.', + type: 'string', + allowedValues: ['major', 'minor', 'patch', 'prerelease'], + supportedManagers: ['helmv3', 'npm', 'nuget', 'maven', 'sbt'], + }, + // Major/Minor/Patch + { + name: 'major', + description: 'Configuration to apply when an update type is `major`.', + stage: 'package', + type: 'object', + default: {}, + cli: false, + mergeable: true, + }, + { + name: 'minor', + description: 'Configuration to apply when an update type is `minor`.', + stage: 'package', + type: 'object', + default: {}, + cli: false, + mergeable: true, + }, + { + name: 'patch', + description: 'Configuration to apply when an update type is `patch`.', + stage: 'package', + type: 'object', + default: {}, + cli: false, + mergeable: true, + }, + { + name: 'pin', + description: 'Configuration to apply when an update type is `pin`.', + stage: 'package', + type: 'object', + default: { + rebaseWhen: 'behind-base-branch', + groupName: 'Pin Dependencies', + groupSlug: 'pin-dependencies', + commitMessageAction: 'Pin', + group: { + commitMessageTopic: 'dependencies', + commitMessageExtra: '', + }, + }, + cli: false, + mergeable: true, + }, + { + name: 'digest', + description: + 'Configuration to apply when updating a digest (no change in tag/version).', + stage: 'package', + type: 'object', + default: { + branchTopic: '{{{depNameSanitized}}}-digest', + commitMessageExtra: 'to {{newDigestShort}}', + commitMessageTopic: '{{{depName}}} digest', + }, + cli: false, + mergeable: true, + }, + { + name: 'pinDigest', + description: + 'Configuration to apply when pinning a digest (no change in tag/version).', + stage: 'package', + type: 'object', + default: { + groupName: 'Pin Dependencies', + groupSlug: 'pin-dependencies', + commitMessageAction: 'Pin', + group: { + commitMessageTopic: 'dependencies', + commitMessageExtra: '', + }, + }, + cli: false, + mergeable: true, + }, + { + name: 'rollback', + description: 'Configuration to apply when rolling back a version.', + stage: 'package', + type: 'object', + default: { + branchTopic: '{{{depNameSanitized}}}-rollback', + commitMessageAction: 'Roll back', + semanticCommitType: 'fix', + }, + cli: false, + mergeable: true, + }, + { + name: 'replacement', + description: 'Configuration to apply when replacing a dependency.', + stage: 'package', + type: 'object', + default: { + branchTopic: '{{{depNameSanitized}}}-replacement', + commitMessageAction: 'Replace', + commitMessageExtra: + 'with {{newName}} {{#if isMajor}}{{{prettyNewMajor}}}{{else}}{{#if isSingleVersion}}{{{prettyNewVersion}}}{{else}}{{{newValue}}}{{/if}}{{/if}}', + prBodyNotes: [ + 'This is a special PR that replaces `{{{depName}}}` with the community suggested minimal stable replacement version.', + ], + }, + cli: false, + mergeable: true, + }, + // Semantic commit / Semantic release + { + name: 'semanticCommits', + description: 'Enable Semantic Commit prefixes for commits and PR titles.', + type: 'string', + allowedValues: ['auto', 'enabled', 'disabled'], + default: 'auto', + }, + { + name: 'semanticCommitType', + description: 'Commit type to use if Semantic Commits is enabled.', + type: 'string', + default: 'chore', + }, + { + name: 'semanticCommitScope', + description: 'Commit scope to use if Semantic Commits are enabled.', + type: 'string', + default: 'deps', + }, + { + name: 'commitMessageLowerCase', + description: 'Lowercase PR- and commit titles.', + type: 'string', + allowedValues: ['auto', 'never'], + default: 'auto', + }, + // PR Behavior + { + name: 'keepUpdatedLabel', + description: + 'If set, users can add this label to PRs to request they be kept updated with the base branch.', + type: 'string', + supportedPlatforms: ['azure', 'gitea', 'github', 'gitlab'], + }, + { + name: 'rollbackPrs', + description: + 'Create PRs to roll back versions if the current version is not found in the registry.', + type: 'boolean', + default: false, + }, + { + name: 'recreateWhen', + description: 'Recreate PRs even if same ones were closed previously.', + type: 'string', + default: 'auto', + allowedValues: ['auto', 'always', 'never'], + }, + { + name: 'rebaseWhen', + description: 'Controls when Renovate rebases an existing branch.', + type: 'string', + allowedValues: ['auto', 'never', 'conflicted', 'behind-base-branch'], + default: 'auto', + }, + { + name: 'rebaseLabel', + description: 'Label to request a rebase from Renovate bot.', + type: 'string', + default: 'rebase', + }, + { + name: 'stopUpdatingLabel', + description: 'Label to make Renovate stop updating a PR.', + type: 'string', + default: 'stop-updating', + supportedPlatforms: ['azure', 'gitea', 'github', 'gitlab'], + }, + { + name: 'minimumReleaseAge', + description: 'Time required before a new release is considered stable.', + type: 'string', + default: null, + }, + { + name: 'internalChecksAsSuccess', + description: + 'Whether to consider passing internal checks such as `minimumReleaseAge` when determining branch status.', + type: 'boolean', + default: false, + }, + /* + * Undocumented experimental feature + { + name: 'minimumConfidence', + description: + 'Minimum Merge confidence level to filter by. Requires authentication to work.', + type: 'string', + allowedValues: ['low', 'neutral', 'high', 'very high'], + default: 'low', + }, + */ + { + name: 'internalChecksFilter', + description: 'When and how to filter based on internal checks.', + type: 'string', + allowedValues: ['strict', 'flexible', 'none'], + default: 'strict', + }, + { + name: 'prCreation', + description: 'When to create the PR for a branch.', + type: 'string', + allowedValues: ['immediate', 'not-pending', 'status-success', 'approval'], + default: 'immediate', + }, + { + name: 'prNotPendingHours', + description: 'Timeout in hours for when `prCreation=not-pending`.', + type: 'integer', + default: 25, + }, + { + name: 'prHourlyLimit', + description: + 'Rate limit PRs to maximum x created per hour. 0 means no limit.', + type: 'integer', + default: 2, + }, + { + name: 'prConcurrentLimit', + description: + 'Limit to a maximum of x concurrent branches/PRs. 0 means no limit.', + type: 'integer', + default: 10, + }, + { + name: 'branchConcurrentLimit', + description: + 'Limit to a maximum of x concurrent branches. 0 means no limit, `null` (default) inherits value from `prConcurrentLimit`.', + type: 'integer', + default: null, // inherit prConcurrentLimit + }, + { + name: 'prPriority', + description: + 'Set sorting priority for PR creation. PRs with higher priority are created first, negative priority last.', + type: 'integer', + allowNegative: true, + default: 0, + parents: ['packageRules'], + cli: false, + env: false, + }, + { + name: 'bbUseDefaultReviewers', + description: 'Use the default reviewers (Bitbucket only).', + type: 'boolean', + default: true, + supportedPlatforms: ['bitbucket', 'bitbucket-server'], + }, + { + name: 'bbUseDevelopmentBranch', + description: `Use the repository's [development branch](https://support.atlassian.com/bitbucket-cloud/docs/branch-a-repository/#The-branching-model) as the repository's default branch.`, + type: 'boolean', + default: false, + supportedPlatforms: ['bitbucket'], + globalOnly: true, + inheritConfigSupport: true, + }, + // Automatic merging + { + name: 'automerge', + description: + 'Whether to automerge branches/PRs automatically, without human intervention.', + type: 'boolean', + default: false, + }, + { + name: 'automergeType', + description: 'How to automerge, if enabled.', + type: 'string', + allowedValues: ['branch', 'pr', 'pr-comment'], + default: 'pr', + }, + { + name: 'automergeStrategy', + description: + 'The merge strategy to use when automerging PRs. Used only if `automergeType=pr`.', + type: 'string', + allowedValues: ['auto', 'fast-forward', 'merge-commit', 'rebase', 'squash'], + default: 'auto', + supportedPlatforms: ['azure', 'bitbucket', 'gitea'], + }, + { + name: 'automergeComment', + description: + 'PR comment to add to trigger automerge. Only used if `automergeType=pr-comment`.', + type: 'string', + default: 'automergeComment', + }, + { + name: 'ignoreTests', + description: 'Set to `true` to enable automerging without tests.', + type: 'boolean', + default: false, + }, + { + name: 'transformTemplates', + description: 'List of jsonata transformation rules.', + type: 'array', + subType: 'string', + parents: ['customDatasources'], + default: [], + }, + { + name: 'vulnerabilityAlerts', + description: + 'Config to apply when a PR is needed due to a vulnerability in the existing package version.', + type: 'object', + default: { + groupName: null, + schedule: [], + dependencyDashboardApproval: false, + minimumReleaseAge: null, + rangeStrategy: 'update-lockfile', + commitMessageSuffix: '[SECURITY]', + branchTopic: `{{{datasource}}}-{{{depNameSanitized}}}-vulnerability`, + prCreation: 'immediate', + }, + mergeable: true, + cli: false, + env: false, + supportedPlatforms: ['github'], + }, + { + name: 'osvVulnerabilityAlerts', + description: 'Use vulnerability alerts from `osv.dev`.', + type: 'boolean', + default: false, + experimental: true, + experimentalIssues: [20542], + }, + { + name: 'pruneBranchAfterAutomerge', + description: 'Set to `true` to enable branch pruning after automerging.', + type: 'boolean', + default: true, + }, + // Default templates + { + name: 'branchName', + description: 'Branch name template.', + type: 'string', + default: '{{{branchPrefix}}}{{{additionalBranchPrefix}}}{{{branchTopic}}}', + deprecationMsg: + 'We strongly recommended that you avoid configuring this field directly. Please edit `branchPrefix`, `additionalBranchPrefix`, or `branchTopic` instead.', + cli: false, + }, + { + name: 'additionalBranchPrefix', + description: 'Additional string value to be appended to `branchPrefix`.', + type: 'string', + default: '', + cli: false, + }, + { + name: 'branchTopic', + description: 'Branch topic.', + type: 'string', + default: + '{{{depNameSanitized}}}-{{{newMajor}}}{{#if separateMinorPatch}}{{#if isPatch}}.{{{newMinor}}}{{/if}}{{/if}}.x{{#if isLockfileUpdate}}-lockfile{{/if}}', + cli: false, + }, + { + name: 'commitMessage', + description: 'Message to use for commit messages and pull request titles.', + type: 'string', + default: + '{{{commitMessagePrefix}}} {{{commitMessageAction}}} {{{commitMessageTopic}}} {{{commitMessageExtra}}} {{{commitMessageSuffix}}}', + deprecationMsg: + 'We deprecated editing the `commitMessage` directly, and we recommend you stop using this config option. Instead use config options like `commitMessageAction`, `commitMessageExtra`, and so on, to create the commit message you want.', + cli: false, + }, + { + name: 'commitBody', + description: + 'Commit message body template. Will be appended to commit message, separated by two line returns.', + type: 'string', + cli: false, + }, + { + name: 'commitBodyTable', + description: + 'If enabled, append a table in the commit message body describing all updates in the commit.', + type: 'boolean', + default: false, + }, + { + name: 'commitMessagePrefix', + description: + 'Prefix to add to start of commit messages and PR titles. Uses a semantic prefix if `semanticCommits` is enabled.', + type: 'string', + cli: false, + advancedUse: true, + }, + { + name: 'commitMessageAction', + description: 'Action verb to use in commit messages and PR titles.', + type: 'string', + default: 'Update', + cli: false, + advancedUse: true, + }, + { + name: 'commitMessageTopic', + description: + 'The upgrade topic/noun used in commit messages and PR titles.', + type: 'string', + default: 'dependency {{depName}}', + cli: false, + advancedUse: true, + }, + { + name: 'commitMessageExtra', + description: + 'Extra description used after the commit message topic - typically the version.', + type: 'string', + default: + 'to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{prettyNewMajor}}{{else}}{{#if isSingleVersion}}{{prettyNewVersion}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}', + cli: false, + advancedUse: true, + }, + { + name: 'commitMessageSuffix', + description: 'Suffix to add to end of commit messages and PR titles.', + type: 'string', + cli: false, + advancedUse: true, + }, + { + name: 'prBodyTemplate', + description: + 'Pull Request body template. Controls which sections are rendered in the body of the pull request.', + type: 'string', + default: + '{{{header}}}{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}{{{configDescription}}}{{{controls}}}{{{footer}}}', + cli: false, + }, + { + name: 'prTitle', + description: + 'Pull Request title template. Inherits from `commitMessage` if null.', + type: 'string', + default: null, + deprecationMsg: + 'Direct editing of `prTitle` is now deprecated. Instead use config options like `commitMessageAction`, `commitMessageExtra`, and so on, as they will be passed through to `prTitle`.', + cli: false, + }, + { + name: 'prTitleStrict', + description: + 'Whether to bypass appending extra context to the Pull Request title.', + type: 'boolean', + default: false, + experimental: true, + cli: false, + }, + { + name: 'prHeader', + description: 'Text added here will be placed first in the PR body.', + type: 'string', + }, + { + name: 'prFooter', + description: + 'Text added here will be placed last in the PR body, with a divider separator before it.', + type: 'string', + default: `This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).`, + }, + { + name: 'customizeDashboard', + description: 'Customize sections in the Dependency Dashboard issue.', + type: 'object', + default: {}, + freeChoice: true, + additionalProperties: { + type: 'string', + }, + }, + { + name: 'lockFileMaintenance', + description: 'Configuration for lock file maintenance.', + stage: 'branch', + type: 'object', + default: { + enabled: false, + recreateWhen: 'always', + rebaseStalePrs: true, + branchTopic: 'lock-file-maintenance', + commitMessageAction: 'Lock file maintenance', + commitMessageTopic: null, + commitMessageExtra: null, + schedule: ['before 4am on monday'], + groupName: null, + prBodyDefinitions: { + Change: 'All locks refreshed', + }, + }, + cli: false, + mergeable: true, + }, + { + name: 'hashedBranchLength', + description: + 'If enabled, branch names will use a hashing function to ensure each branch has that length.', + type: 'integer', + default: null, + cli: false, + }, + // Dependency Groups + { + name: 'groupName', + description: 'Human understandable name for the dependency group.', + type: 'string', + default: null, + }, + { + name: 'groupSlug', + description: + 'Slug to use for group (e.g. in branch name). Slug is calculated from `groupName` if `null`.', + type: 'string', + default: null, + cli: false, + env: false, + }, + { + name: 'group', + description: 'Config if `groupName` is enabled.', + type: 'object', + default: { + branchTopic: '{{{groupSlug}}}', + commitMessageTopic: '{{{groupName}}}', + }, + cli: false, + env: false, + mergeable: true, + advancedUse: true, + }, + // Pull Request options + { + name: 'labels', + description: 'Labels to set in Pull Request.', + type: 'array', + subType: 'string', + }, + { + name: 'addLabels', + description: 'Labels to add to Pull Request.', + type: 'array', + subType: 'string', + mergeable: true, + }, + { + name: 'assignees', + description: + 'Assignees for Pull Request (either username or email address depending on the platform).', + type: 'array', + subType: 'string', + }, + { + name: 'assigneesFromCodeOwners', + description: + 'Determine assignees based on configured code owners and changes in PR.', + type: 'boolean', + default: false, + }, + { + name: 'expandCodeOwnersGroups', + description: + 'Expand the configured code owner groups into a full list of group members.', + type: 'boolean', + default: false, + supportedPlatforms: ['gitlab'], + }, + { + name: 'assigneesSampleSize', + description: 'Take a random sample of given size from `assignees`.', + type: 'integer', + default: null, + }, + { + name: 'assignAutomerge', + description: + 'Assign reviewers and assignees even if the PR is to be automerged.', + type: 'boolean', + default: false, + }, + { + name: 'ignoreReviewers', + description: + 'Reviewers to be ignored in PR reviewers presence (either username or email address depending on the platform).', + type: 'array', + subType: 'string', + }, + { + name: 'reviewers', + description: + 'Requested reviewers for Pull Requests (either username or email address depending on the platform).', + type: 'array', + subType: 'string', + }, + { + name: 'reviewersFromCodeOwners', + description: + 'Determine reviewers based on configured code owners and changes in PR.', + type: 'boolean', + default: false, + }, + { + name: 'filterUnavailableUsers', + description: 'Filter reviewers and assignees based on their availability.', + type: 'boolean', + default: false, + supportedPlatforms: ['gitlab'], + }, + { + name: 'forkModeDisallowMaintainerEdits', + description: + 'Disallow maintainers to push to Renovate pull requests when running in fork mode.', + type: 'boolean', + default: false, + supportedPlatforms: ['github'], + }, + { + name: 'confidential', + description: + 'If enabled, issues created by Renovate are set as confidential.', + type: 'boolean', + default: false, + supportedPlatforms: ['gitlab'], + }, + { + name: 'reviewersSampleSize', + description: 'Take a random sample of given size from `reviewers`.', + type: 'integer', + default: null, + }, + { + name: 'additionalReviewers', + description: + 'Additional reviewers for Pull Requests (in contrast to `reviewers`, this option adds to the existing reviewer list, rather than replacing it).', + type: 'array', + subType: 'string', + mergeable: true, + }, + { + name: 'fileMatch', + description: 'RegEx (`re2`) pattern for matching manager files.', + type: 'array', + subType: 'string', + format: 'regex', + stage: 'repository', + allowString: true, + mergeable: true, + cli: false, + env: false, + }, + { + name: 'postUpdateOptions', + description: + 'Enable post-update options to be run after package/artifact updating.', + type: 'array', + default: [], + subType: 'string', + allowedValues: [ + 'bundlerConservative', + 'gomodMassage', + 'gomodTidy', + 'gomodTidy1.17', + 'gomodTidyE', + 'gomodUpdateImportPaths', + 'helmUpdateSubChartArchives', + 'npmDedupe', + 'pnpmDedupe', + 'yarnDedupeFewer', + 'yarnDedupeHighest', + ], + cli: false, + env: false, + mergeable: true, + }, + { + name: 'constraints', + description: + 'Configuration object to define language or manager version constraints.', + type: 'object', + default: {}, + mergeable: true, + cli: false, + supportedManagers: [ + 'bundler', + 'composer', + 'gomod', + 'npm', + 'pep621', + 'pipenv', + 'poetry', + ], + }, + { + name: 'hostRules', + description: 'Host rules/configuration including credentials.', + type: 'array', + subType: 'object', + stage: 'repository', + cli: true, + mergeable: true, + }, + { + name: 'hostType', + description: + 'hostType for a package rule. Can be a platform name or a datasource name.', + type: 'string', + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'matchHost', + description: 'A domain name, host name or base URL to match against.', + type: 'string', + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'readOnly', + description: + 'Match against requests that only read data and do not mutate anything.', + type: 'boolean', + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'timeout', + description: 'Timeout (in milliseconds) for queries to external endpoints.', + type: 'integer', + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'insecureRegistry', + description: 'Explicitly turn on insecure Docker registry access (HTTP).', + type: 'boolean', + default: false, + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + advancedUse: true, + }, + { + name: 'abortOnError', + description: + 'If enabled, Renovate aborts its run when HTTP request errors occur.', + type: 'boolean', + default: false, + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'abortIgnoreStatusCodes', + description: + 'A list of HTTP status codes safe to ignore even when `abortOnError=true`.', + type: 'array', + subType: 'number', + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'enableHttp2', + description: 'Enable got HTTP/2 support.', + type: 'boolean', + default: false, + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'concurrentRequestLimit', + description: 'Limit concurrent requests per host.', + type: 'integer', + stage: 'repository', + parents: ['hostRules'], + default: null, + cli: false, + env: false, + }, + { + name: 'maxRequestsPerSecond', + description: 'Limit requests rate per host.', + type: 'integer', + stage: 'repository', + parents: ['hostRules'], + default: 0, + cli: false, + env: false, + }, + { + name: 'authType', + description: + 'Authentication type for HTTP header. e.g. `"Bearer"` or `"Basic"`. Use `"Token-Only"` to use only the token without an authorization type.', + type: 'string', + stage: 'repository', + parents: ['hostRules'], + default: 'Bearer', + cli: false, + env: false, + }, + { + name: 'dnsCache', + description: 'Enable got DNS cache.', + type: 'boolean', + default: false, + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + experimental: true, + deprecationMsg: + 'This option is deprecated and will be removed in a future release.', + }, + { + name: 'keepAlive', + description: 'Enable HTTP keep-alive for hosts.', + type: 'boolean', + default: false, + stage: 'repository', + parents: ['hostRules'], + cli: false, + env: false, + advancedUse: true, + }, + { + name: 'headers', + description: + 'Put fields to be forwarded to the HTTP request headers in the headers config option.', + type: 'object', + parents: ['hostRules'], + cli: false, + env: false, + advancedUse: true, + }, + { + name: 'artifactAuth', + description: + 'A list of package managers to enable artifact auth. Only managers on the list are enabled. All are enabled if `null`.', + experimental: true, + type: 'array', + subType: 'string', + stage: 'repository', + parents: ['hostRules'], + allowedValues: ['composer'], + default: null, + cli: false, + env: false, + }, + { + name: 'httpsCertificateAuthority', + description: 'The overriding trusted CA certificate.', + type: 'string', + stage: 'repository', + parents: ['hostRules'], + default: null, + cli: false, + env: false, + }, + { + name: 'httpsPrivateKey', + description: 'The private key in PEM format.', + type: 'string', + stage: 'repository', + parents: ['hostRules'], + default: null, + cli: false, + env: false, + }, + { + name: 'httpsCertificate', + description: 'The certificate chains in PEM format.', + type: 'string', + stage: 'repository', + parents: ['hostRules'], + default: null, + cli: false, + env: false, + }, + { + name: 'cacheHardTtlMinutes', + description: + 'Maximum duration in minutes to keep datasource cache entries.', + type: 'integer', + stage: 'repository', + default: 7 * 24 * 60, + globalOnly: true, + }, + { + name: 'cacheTtlOverride', + description: 'An object that contains cache namespace TTL override values.', + type: 'object', + stage: 'repository', + default: {}, + globalOnly: true, + experimental: true, + advancedUse: true, + }, + { + name: 'prBodyDefinitions', + description: 'Table column definitions to use in PR tables.', + type: 'object', + freeChoice: true, + mergeable: true, + default: { + Package: '{{{depNameLinked}}}', + Type: '{{{depType}}}', + Update: '{{{updateType}}}', + 'Current value': '{{{currentValue}}}', + 'New value': '{{{newValue}}}', + Change: '`{{{displayFrom}}}` -> `{{{displayTo}}}`', + Pending: '{{{displayPending}}}', + References: '{{{references}}}', + 'Package file': '{{{packageFile}}}', + Age: "{{#if newVersion}}[![age](https://developer.mend.io/api/mc/badges/age/{{datasource}}/{{replace '/' '%2f' depName}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", + Adoption: + "{{#if newVersion}}[![adoption](https://developer.mend.io/api/mc/badges/adoption/{{datasource}}/{{replace '/' '%2f' depName}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", + Passing: + "{{#if newVersion}}[![passing](https://developer.mend.io/api/mc/badges/compatibility/{{datasource}}/{{replace '/' '%2f' depName}}/{{{currentVersion}}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", + Confidence: + "{{#if newVersion}}[![confidence](https://developer.mend.io/api/mc/badges/confidence/{{datasource}}/{{replace '/' '%2f' depName}}/{{{currentVersion}}}/{{{newVersion}}}?slim=true)](https://docs.renovatebot.com/merge-confidence/){{/if}}", + }, + }, + { + name: 'prBodyColumns', + description: 'List of columns to use in PR bodies.', + type: 'array', + subType: 'string', + default: ['Package', 'Type', 'Update', 'Change', 'Pending'], + }, + { + name: 'prBodyNotes', + description: + 'List of extra notes or templates to include in the Pull Request body.', + type: 'array', + subType: 'string', + default: [], + allowString: true, + mergeable: true, + }, + { + name: 'suppressNotifications', + description: + 'Options to suppress various types of warnings and other notifications.', + type: 'array', + subType: 'string', + default: ['deprecationWarningIssues'], + allowedValues: [ + 'artifactErrors', + 'branchAutomergeFailure', + 'configErrorIssue', + 'dependencyLookupWarnings', + 'deprecationWarningIssues', + 'lockFileErrors', + 'missingCredentialsError', + 'onboardingClose', + 'prEditedNotification', + 'prIgnoreNotification', + ], + cli: false, + env: false, + mergeable: true, + }, + { + name: 'pruneStaleBranches', + description: 'Set to `false` to disable pruning stale branches.', + type: 'boolean', + default: true, + }, + { + name: 'unicodeEmoji', + description: 'Enable or disable Unicode emoji.', + type: 'boolean', + default: true, + globalOnly: true, + }, + { + name: 'gitLabIgnoreApprovals', + description: `Ignore approval rules for MRs created by Renovate, which is useful for automerge.`, + type: 'boolean', + default: false, + }, + { + name: 'customManagers', + description: 'Custom managers using regex matching.', + type: 'array', + subType: 'object', + default: [], + stage: 'package', + cli: true, + mergeable: true, + }, + { + name: 'customType', + description: + 'Custom manager to use. Valid only within a `customManagers` object.', + type: 'string', + allowedValues: ['regex'], + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'matchStrings', + description: + 'Regex capture rule to use. Valid only within a `customManagers` object.', + type: 'array', + subType: 'string', + format: 'regex', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'matchStringsStrategy', + description: 'Strategy how to interpret matchStrings.', + type: 'string', + default: 'any', + allowedValues: ['any', 'recursive', 'combination'], + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'depNameTemplate', + description: + 'Optional depName for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'packageNameTemplate', + description: + 'Optional packageName for extracted dependencies, else defaults to `depName` value. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'datasourceTemplate', + description: + 'Optional datasource for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'depTypeTemplate', + description: + 'Optional `depType` for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'currentValueTemplate', + description: + 'Optional `currentValue` for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'versioningTemplate', + description: + 'Optional versioning for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'registryUrlTemplate', + description: + 'Optional registry URL for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'extractVersionTemplate', + description: + 'Optional `extractVersion` for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'autoReplaceStringTemplate', + description: + 'Optional `extractVersion` for extracted dependencies. Valid only within a `customManagers` object.', + type: 'string', + parents: ['customManagers'], + cli: false, + env: false, + }, + { + name: 'fetchChangeLogs', + description: 'Controls if and when changelogs/release notes are fetched.', + type: 'string', + allowedValues: ['off', 'branch', 'pr'], + default: 'pr', + cli: false, + env: false, + }, + { + name: 'cloneSubmodules', + description: + 'Set to `true` to initialize submodules during repository clone.', + type: 'boolean', + default: false, + }, + { + name: 'ignorePrAuthor', + description: + 'Set to `true` to fetch the entire list of PRs instead of only those authored by the Renovate user.', + type: 'boolean', + default: false, + }, + { + name: 'gitNoVerify', + description: + 'Which Git commands will be run with the `--no-verify` option.', + type: 'array', + subType: 'string', + allowString: true, + allowedValues: ['commit', 'push'], + default: ['commit', 'push'], + stage: 'global', + globalOnly: true, + }, + { + name: 'updatePinnedDependencies', + description: + 'Whether to update pinned (single version) dependencies or not.', + type: 'boolean', + default: true, + }, + { + name: 'gitUrl', + description: + 'Overrides the default resolution for Git remote, e.g. to switch GitLab from HTTPS to SSH-based.', + type: 'string', + supportedPlatforms: ['gitlab', 'bitbucket-server'], + allowedValues: ['default', 'ssh', 'endpoint'], + default: 'default', + stage: 'repository', + globalOnly: true, + }, + { + name: 'writeDiscoveredRepos', + description: 'Writes discovered repositories to a JSON file and then exit.', + type: 'string', + globalOnly: true, + env: false, + }, + { + name: 'platformAutomerge', + description: `Controls if platform-native auto-merge is used.`, + type: 'boolean', + default: true, + supportedPlatforms: ['azure', 'gitea', 'github', 'gitlab'], + }, + { + name: 'userStrings', + description: + 'User-facing strings for the Renovate comment when a PR is closed.', + type: 'object', + freeChoice: true, + default: { + ignoreTopic: 'Renovate Ignore Notification', + ignoreMajor: + 'Because you closed this PR without merging, Renovate will ignore this update. You will not get PRs for *any* future `{{{newMajor}}}.x` releases. But if you manually upgrade to `{{{newMajor}}}.x` then Renovate will re-enable `minor` and `patch` updates automatically.', + ignoreDigest: + 'Because you closed this PR without merging, Renovate will ignore this update. You will not get PRs for the `{{{depName}}}` `{{{newDigestShort}}}` update again.', + ignoreOther: + 'Because you closed this PR without merging, Renovate will ignore this update (`{{{newValue}}}`). You will get a PR once a newer version is released. To ignore this dependency forever, add it to the `ignoreDeps` array of your Renovate config.', + }, + }, + { + name: 'platformCommit', + description: `Use platform API to perform commits instead of using Git directly.`, + type: 'boolean', + default: false, + supportedPlatforms: ['github'], + }, + { + name: 'branchNameStrict', + description: `Whether to be strict about the use of special characters within the branch name.`, + type: 'boolean', + default: false, + }, + { + name: 'checkedBranches', + description: + 'A list of branch names to mark for creation or rebasing as if it was selected in the Dependency Dashboard issue.', + type: 'array', + subType: 'string', + experimental: true, + globalOnly: true, + default: [], + }, + { + name: 'maxRetryAfter', + description: + 'Maximum retry-after header value to wait for before retrying a failed request.', + type: 'integer', + default: 60, + stage: 'package', + parents: ['hostRules'], + cli: false, + env: false, + }, + { + name: 'logLevelRemap', + description: 'Remap log levels to different levels.', + type: 'array', + subType: 'object', + stage: 'repository', + cli: false, + env: false, + }, + { + name: 'matchMessage', + description: 'Regex/minimatch expression to match against log message.', + type: 'string', + parents: ['logLevelRemap'], + cli: false, + env: false, + }, + { + name: 'newLogLevel', + description: 'New log level to use if matchMessage matches.', + type: 'string', + allowedValues: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'], + parents: ['logLevelRemap'], + cli: false, + env: false, + }, + { + name: 'milestone', + description: `The number of a milestone. If set, the milestone will be set when Renovate creates the PR.`, + type: 'integer', + default: null, + supportedPlatforms: ['github'], + }, + { + name: 'httpCacheTtlDays', + description: 'Maximum duration in days to keep HTTP cache entries.', + type: 'integer', + stage: 'repository', + default: 90, + globalOnly: true, + }, +]; + +export function getOptions(): RenovateOptions[] { + return options; +} + +function loadManagerOptions(): void { + const allManagers = new Map([...getManagers(), ...getCustomManagers()]); + for (const [name, config] of allManagers.entries()) { + if (config.defaultConfig) { + const managerConfig: RenovateOptions = { + name, + description: `Configuration object for the ${name} manager`, + stage: 'package', + type: 'object', + default: config.defaultConfig, + mergeable: true, + cli: false, + autogenerated: true, + }; + options.push(managerConfig); + } + } +} + +loadManagerOptions(); diff --git a/lib/config/presets/__snapshots__/index.spec.ts.snap b/lib/config/presets/__snapshots__/index.spec.ts.snap index b3fac71cdca566..433ee0a7f9861a 100644 --- a/lib/config/presets/__snapshots__/index.spec.ts.snap +++ b/lib/config/presets/__snapshots__/index.spec.ts.snap @@ -56,7 +56,7 @@ exports[`config/presets/index resolvePreset migrates automerge in presets 1`] = }, "packageRules": [ { - "matchPackagePatterns": [ + "matchPackageNames": [ "*", ], "semanticCommitType": "chore", @@ -83,7 +83,7 @@ exports[`config/presets/index resolvePreset migrates automerge in presets 1`] = "semanticCommitType": "fix", }, { - "matchPackagePatterns": [ + "matchPackageNames": [ "*", ], "rangeStrategy": "replace", @@ -120,13 +120,10 @@ exports[`config/presets/index resolvePreset resolves eslint 1`] = ` "@types/eslint", "babel-eslint", "@babel/eslint-parser", - ], - "matchPackagePrefixes": [ - "@eslint/", - "@stylistic/eslint-plugin", - "@types/eslint__", - "@typescript-eslint/", - "eslint", + "@eslint/**", + "@types/eslint__**", + "@typescript-eslint/**", + "eslint**", ], } `; @@ -137,30 +134,24 @@ exports[`config/presets/index resolvePreset resolves linters 1`] = ` "All lint-related packages.", ], "matchPackageNames": [ + "ember-template-lint**", "@types/eslint", "babel-eslint", "@babel/eslint-parser", + "@eslint/**", + "@types/eslint__**", + "@typescript-eslint/**", + "eslint**", "friendsofphp/php-cs-fixer", "squizlabs/php_codesniffer", "symplify/easy-coding-standard", - "@stylistic/stylelint-plugin", + "stylelint**", "codelyzer", + "/\\btslint\\b/", "prettier", "remark-lint", "standard", ], - "matchPackagePatterns": [ - "\\btslint\\b", - ], - "matchPackagePrefixes": [ - "ember-template-lint", - "@eslint/", - "@stylistic/eslint-plugin", - "@types/eslint__", - "@typescript-eslint/", - "eslint", - "stylelint", - ], } `; @@ -176,30 +167,24 @@ exports[`config/presets/index resolvePreset resolves nested groups 1`] = ` "All lint-related packages.", ], "matchPackageNames": [ + "ember-template-lint**", "@types/eslint", "babel-eslint", "@babel/eslint-parser", + "@eslint/**", + "@types/eslint__**", + "@typescript-eslint/**", + "eslint**", "friendsofphp/php-cs-fixer", "squizlabs/php_codesniffer", "symplify/easy-coding-standard", - "@stylistic/stylelint-plugin", + "stylelint**", "codelyzer", + "/\\btslint\\b/", "prettier", "remark-lint", "standard", ], - "matchPackagePatterns": [ - "\\btslint\\b", - ], - "matchPackagePrefixes": [ - "ember-template-lint", - "@eslint/", - "@stylistic/eslint-plugin", - "@types/eslint__", - "@typescript-eslint/", - "eslint", - "stylelint", - ], }, ], } diff --git a/lib/config/presets/index.spec.ts b/lib/config/presets/index.spec.ts index a55ae336459288..8443eea157fe4e 100644 --- a/lib/config/presets/index.spec.ts +++ b/lib/config/presets/index.spec.ts @@ -229,27 +229,6 @@ describe('config/presets/index', () => { expect(e!.validationMessage).toBeUndefined(); }); - it('combines two package alls', async () => { - config.extends = ['packages:eslint', 'packages:stylelint']; - const res = await presets.resolveConfigPresets(config); - expect(res).toEqual({ - matchPackageNames: [ - '@types/eslint', - 'babel-eslint', - '@babel/eslint-parser', - '@stylistic/stylelint-plugin', - ], - matchPackagePrefixes: [ - '@eslint/', - '@stylistic/eslint-plugin', - '@types/eslint__', - '@typescript-eslint/', - 'eslint', - 'stylelint', - ], - }); - }); - it('resolves packageRule', async () => { config.packageRules = [ { @@ -266,13 +245,10 @@ describe('config/presets/index', () => { '@types/eslint', 'babel-eslint', '@babel/eslint-parser', - ], - matchPackagePrefixes: [ - '@eslint/', - '@stylistic/eslint-plugin', - '@types/eslint__', - '@typescript-eslint/', - 'eslint', + '@eslint/**', + '@types/eslint__**', + '@typescript-eslint/**', + 'eslint**', ], }, ], @@ -283,16 +259,14 @@ describe('config/presets/index', () => { config.extends = ['packages:eslint']; const res = await presets.resolveConfigPresets(config); expect(res).toMatchSnapshot(); - expect(res.matchPackagePrefixes).toHaveLength(5); + expect(res.matchPackageNames).toHaveLength(7); }); it('resolves linters', async () => { config.extends = ['packages:linters']; const res = await presets.resolveConfigPresets(config); expect(res).toMatchSnapshot(); - expect(res.matchPackageNames).toHaveLength(11); - expect(res.matchPackagePatterns).toHaveLength(1); - expect(res.matchPackagePrefixes).toHaveLength(7); + expect(res.matchPackageNames).toHaveLength(17); }); it('resolves nested groups', async () => { @@ -301,9 +275,7 @@ describe('config/presets/index', () => { expect(res).toMatchSnapshot(); const rule = res.packageRules![0]; expect(rule.automerge).toBeTrue(); - expect(rule.matchPackageNames).toHaveLength(11); - expect(rule.matchPackagePatterns).toHaveLength(1); - expect(rule.matchPackagePrefixes).toHaveLength(7); + expect(rule.matchPackageNames).toHaveLength(17); }); it('migrates automerge in presets', async () => { diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 93e8e1fc082399..9a96ed1f6f5aab 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -288,15 +288,7 @@ export async function getPreset( // preset is just a collection of other presets delete presetConfig.description; } - const packageListKeys = [ - 'description', - 'matchPackageNames', - 'excludePackageNames', - 'matchPackagePatterns', - 'excludePackagePatterns', - 'matchPackagePrefixes', - 'excludePackagePrefixes', - ]; + const packageListKeys = ['description', 'matchPackageNames']; if (presetKeys.every((key) => packageListKeys.includes(key))) { delete presetConfig.description; } diff --git a/lib/config/presets/internal/default.ts b/lib/config/presets/internal/default.ts index 29fe0081b4ce92..d06839b4b9d3dd 100644 --- a/lib/config/presets/internal/default.ts +++ b/lib/config/presets/internal/default.ts @@ -115,7 +115,7 @@ export const presets: Record = { packageRules: [ { automerge: true, - matchPackagePrefixes: ['@types/'], + matchPackageNames: ['@types/**'], }, ], }, @@ -375,7 +375,7 @@ export const presets: Record = { description: 'Pin all dependency versions except `peerDependencies`.', packageRules: [ { - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], rangeStrategy: 'pin', }, { @@ -412,7 +412,7 @@ export const presets: Record = { 'Pin dependency versions for `devDependencies` and retain SemVer ranges for others.', packageRules: [ { - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], rangeStrategy: 'replace', }, { @@ -451,7 +451,7 @@ export const presets: Record = { preserveSemverRanges: { description: 'Preserve (but continue to upgrade) any existing SemVer ranges.', - packageRules: [{ matchPackagePatterns: ['*'], rangeStrategy: 'replace' }], + packageRules: [{ matchPackageNames: ['*'], rangeStrategy: 'replace' }], }, prHourlyLimit1: { description: 'Rate limit PR creation to a maximum of one per hour.', @@ -542,7 +542,7 @@ export const presets: Record = { 'Use semantic commit type `fix` for dependencies and `chore` for all others if semantic commits are in use.', packageRules: [ { - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], semanticCommitType: 'chore', }, { diff --git a/lib/config/presets/internal/group.ts b/lib/config/presets/internal/group.ts index f6cf6562cb9f4a..64c7aed8b0acda 100644 --- a/lib/config/presets/internal/group.ts +++ b/lib/config/presets/internal/group.ts @@ -17,7 +17,7 @@ const staticGroups = { { groupName: 'all dependencies', groupSlug: 'all', - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], }, ], @@ -38,7 +38,7 @@ const staticGroups = { { groupName: 'all digest updates', groupSlug: 'all-digest', - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], matchUpdateTypes: ['digest'], }, ], @@ -49,7 +49,7 @@ const staticGroups = { { groupName: 'all non-major dependencies', groupSlug: 'all-minor-patch', - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], matchUpdateTypes: ['minor', 'patch'], }, ], @@ -59,7 +59,7 @@ const staticGroups = { packageRules: [ { groupName: 'CodeMirror', - matchPackagePrefixes: ['@codemirror/'], + matchPackageNames: ['@codemirror/**'], }, ], }, @@ -68,7 +68,7 @@ const staticGroups = { packageRules: [ { groupName: 'definitelyTyped', - matchPackagePrefixes: ['@types/'], + matchPackageNames: ['@types/**'], }, ], }, @@ -78,7 +78,7 @@ const staticGroups = { { groupName: '.NET Core Docker containers', matchDatasources: ['docker'], - matchPackagePrefixes: ['mcr.microsoft.com/dotnet/'], + matchPackageNames: ['mcr.microsoft.com/dotnet/**'], }, ], }, @@ -87,7 +87,7 @@ const staticGroups = { packageRules: [ { groupName: 'Font Awesome', - matchPackagePrefixes: ['@fortawesome/'], + matchPackageNames: ['@fortawesome/**'], }, ], }, @@ -98,8 +98,10 @@ const staticGroups = { 'fusion-core', 'fusion-test-utils', 'fusion-tokens', + 'fusion-plugin-**', + 'fusion-react**', + 'fusion-apollo**', ], - matchPackagePrefixes: ['fusion-plugin-', 'fusion-react', '^usion-apollo'], }, githubArtifactActions: { description: @@ -142,7 +144,7 @@ const staticGroups = { groupName: 'go-openapi packages', groupSlug: 'go-openapi', matchDatasources: ['go'], - matchPackagePrefixes: ['github.com/go-openapi/'], + matchPackageNames: ['github.com/go-openapi/**'], }, ], }, @@ -151,7 +153,7 @@ const staticGroups = { packageRules: [ { groupName: 'hibernate commons', - matchPackagePrefixes: ['org.hibernate.common:'], + matchPackageNames: ['org.hibernate.common:**'], }, ], }, @@ -160,7 +162,7 @@ const staticGroups = { packageRules: [ { groupName: 'hibernate core', - matchPackagePrefixes: ['org.hibernate:'], + matchPackageNames: ['org.hibernate:**'], }, ], }, @@ -169,7 +171,7 @@ const staticGroups = { packageRules: [ { groupName: 'hibernate ogm', - matchPackagePrefixes: ['org.hibernate.ogm:'], + matchPackageNames: ['org.hibernate.ogm:**'], }, ], }, @@ -178,7 +180,7 @@ const staticGroups = { packageRules: [ { groupName: 'hibernate validator', - matchPackagePrefixes: ['org.hibernate.validator:'], + matchPackageNames: ['org.hibernate.validator:**'], }, ], }, @@ -188,7 +190,7 @@ const staticGroups = { { groupName: 'illuminate packages', groupSlug: 'illuminate', - matchPackagePrefixes: ['illuminate/'], + matchPackageNames: ['illuminate/**'], }, ], }, @@ -197,9 +199,9 @@ const staticGroups = { packageRules: [ { groupName: 'jekyll ecosystem packages', - matchSourceUrlPrefixes: [ - 'https://github.com/jekyll/', - 'https://github.com/github/pages-gem', + matchSourceUrls: [ + 'https://github.com/jekyll/**', + 'https://github.com/github/pages-gem**', ], }, ], @@ -269,7 +271,7 @@ const staticGroups = { { groupName: 'JWT Framework packages', matchDatasources: ['packagist'], - matchPackagePrefixes: ['web-token/'], + matchPackageNames: ['web-token/**'], }, ], }, @@ -280,34 +282,34 @@ const staticGroups = { groupName: 'kubernetes packages', groupSlug: 'kubernetes-go', matchDatasources: ['go'], - matchPackagePrefixes: [ - 'k8s.io/api', - 'k8s.io/apiextensions-apiserver', - 'k8s.io/apimachinery', - 'k8s.io/apiserver', - 'k8s.io/cli-runtime', - 'k8s.io/client-go', - 'k8s.io/cloud-provider', - 'k8s.io/cluster-bootstrap', - 'k8s.io/code-generator', - 'k8s.io/component-base', - 'k8s.io/controller-manager', - 'k8s.io/cri-api', + matchPackageNames: [ + 'k8s.io/api**', + 'k8s.io/apiextensions-apiserver**', + 'k8s.io/apimachinery**', + 'k8s.io/apiserver**', + 'k8s.io/cli-runtime**', + 'k8s.io/client-go**', + 'k8s.io/cloud-provider**', + 'k8s.io/cluster-bootstrap**', + 'k8s.io/code-generator**', + 'k8s.io/component-base**', + 'k8s.io/controller-manager**', + 'k8s.io/cri-api**', // 'k8s.io/csi-api', has not go.mod set up and does not follow the versioning of other repos - 'k8s.io/csi-translation-lib', - 'k8s.io/kube-aggregator', - 'k8s.io/kube-controller-manager', - 'k8s.io/kube-proxy', - 'k8s.io/kube-scheduler', - 'k8s.io/kubectl', - 'k8s.io/kubelet', - 'k8s.io/legacy-cloud-providers', - 'k8s.io/metrics', - 'k8s.io/mount-utils', - 'k8s.io/pod-security-admission', - 'k8s.io/sample-apiserver', - 'k8s.io/sample-cli-plugin', - 'k8s.io/sample-controller', + 'k8s.io/csi-translation-lib**', + 'k8s.io/kube-aggregator**', + 'k8s.io/kube-controller-manager**', + 'k8s.io/kube-proxy**', + 'k8s.io/kube-scheduler**', + 'k8s.io/kubectl**', + 'k8s.io/kubelet**', + 'k8s.io/legacy-cloud-providers**', + 'k8s.io/metrics**', + 'k8s.io/mount-utils**', + 'k8s.io/pod-security-admission**', + 'k8s.io/sample-apiserver**', + 'k8s.io/sample-cli-plugin**', + 'k8s.io/sample-controller**', ], }, ], @@ -327,14 +329,14 @@ const staticGroups = { packageRules: [ { commitMessageTopic: 'Node.js', - excludePackageNames: [ - 'calico/node', - 'docker.io/calico/node', - 'kindest/node', - ], matchDatasources: ['docker'], matchDepNames: ['node'], - matchPackagePatterns: ['/node$'], + matchPackageNames: [ + '//node$/', // Ends with "node" + '!calico/node', + '!docker.io/calico/node', + '!kindest/node', + ], }, ], }, @@ -344,7 +346,7 @@ const staticGroups = { { groupName: 'PHPStan packages', matchDatasources: ['packagist'], - matchPackagePatterns: ['^phpstan/phpstan$', '/phpstan-', '/larastan'], + matchPackageNames: ['phpstan/phpstan', '//phpstan-/', '//larastan/'], }, ], }, @@ -353,7 +355,7 @@ const staticGroups = { packageRules: [ { groupName: 'polymer packages', - matchPackagePrefixes: ['@polymer/'], + matchPackageNames: ['@polymer/**'], }, ], }, @@ -374,35 +376,35 @@ const staticGroups = { groupName: 'Pulumi', groupSlug: 'pulumi-node', matchDatasources: ['npm'], - matchPackagePrefixes: ['@pulumi/'], + matchPackageNames: ['@pulumi/**'], }, { description: 'Group Pulumi Python packages together.', groupName: 'Pulumi', groupSlug: 'pulumi-python', matchDatasources: ['pypi'], - matchPackagePrefixes: ['pulumi-'], + matchPackageNames: ['pulumi-**'], }, { description: 'Group Pulumi Go packages together.', groupName: 'Pulumi', groupSlug: 'pulumi-go', matchDatasources: ['go'], - matchPackagePrefixes: ['github.com/pulumi/'], + matchPackageNames: ['github.com/pulumi/**'], }, { description: 'Group Pulumi Java packages together.', groupName: 'Pulumi', groupSlug: 'pulumi-java', matchDatasources: ['maven'], - matchPackagePrefixes: ['com.pulumi'], + matchPackageNames: ['com.pulumi**'], }, { description: 'Group Pulumi .NET packages together.', groupName: 'Pulumi', groupSlug: 'pulumi-dotnet', matchDatasources: ['nuget'], - matchPackagePrefixes: ['Pulumi'], + matchPackageNames: ['Pulumi**'], }, ], }, @@ -478,7 +480,7 @@ const staticGroups = { { groupName: 'remark', matchDatasources: ['npm'], - matchSourceUrlPrefixes: ['https://github.com/remarkjs/'], + matchSourceUrls: ['https://github.com/remarkjs/**'], }, ], }, @@ -487,7 +489,7 @@ const staticGroups = { packageRules: [ { groupName: 'resilience4j', - matchPackagePrefixes: ['io.github.resilience4j:'], + matchPackageNames: ['io.github.resilience4j:**'], }, ], }, @@ -497,7 +499,7 @@ const staticGroups = { { groupName: 'omniauth packages', matchDatasources: ['rubygems'], - matchPackagePrefixes: ['omniauth'], + matchPackageNames: ['omniauth**'], }, ], }, @@ -530,7 +532,7 @@ const staticGroups = { packageRules: [ { groupName: 'socket.io packages', - matchPackagePrefixes: ['socket.io'], + matchPackageNames: ['socket.io**'], }, ], }, @@ -539,7 +541,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring amqp', - matchPackagePrefixes: ['org.springframework.amqp:'], + matchPackageNames: ['org.springframework.amqp:**'], }, ], }, @@ -548,7 +550,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring android', - matchPackagePrefixes: ['org.springframework.android:'], + matchPackageNames: ['org.springframework.android:**'], }, ], }, @@ -557,7 +559,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring batch', - matchPackagePrefixes: ['org.springframework.batch:'], + matchPackageNames: ['org.springframework.batch:**'], }, ], }, @@ -567,7 +569,7 @@ const staticGroups = { { groupName: 'spring boot', matchDepNames: ['org.springframework.boot'], - matchPackagePrefixes: ['org.springframework.boot:'], + matchPackageNames: ['org.springframework.boot:**'], }, ], }, @@ -576,7 +578,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring cloud', - matchPackagePrefixes: ['org.springframework.cloud:'], + matchPackageNames: ['org.springframework.cloud:**'], }, ], }, @@ -585,7 +587,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring core', - matchPackagePrefixes: ['org.springframework:'], + matchPackageNames: ['org.springframework:**'], }, ], }, @@ -594,7 +596,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring data', - matchPackagePrefixes: ['org.springframework.data:'], + matchPackageNames: ['org.springframework.data:**'], }, ], }, @@ -603,7 +605,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring hateoas', - matchPackagePrefixes: ['org.springframework.hateoas:'], + matchPackageNames: ['org.springframework.hateoas:**'], }, ], }, @@ -612,7 +614,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring integration', - matchPackagePrefixes: ['org.springframework.integration:'], + matchPackageNames: ['org.springframework.integration:**'], }, ], }, @@ -621,7 +623,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring kafka', - matchPackagePrefixes: ['org.springframework.kafka:'], + matchPackageNames: ['org.springframework.kafka:**'], }, ], }, @@ -630,7 +632,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring ldap', - matchPackagePrefixes: ['org.springframework.ldap:'], + matchPackageNames: ['org.springframework.ldap:**'], }, ], }, @@ -639,7 +641,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring mobile', - matchPackagePrefixes: ['org.springframework.mobile:'], + matchPackageNames: ['org.springframework.mobile:**'], }, ], }, @@ -648,7 +650,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring osgi', - matchPackagePrefixes: ['org.springframework.osgi:'], + matchPackageNames: ['org.springframework.osgi:**'], }, ], }, @@ -657,7 +659,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring restdocs', - matchPackagePrefixes: ['org.springframework.restdocs:'], + matchPackageNames: ['org.springframework.restdocs:**'], }, ], }, @@ -666,7 +668,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring roo', - matchPackagePrefixes: ['org.springframework.roo:'], + matchPackageNames: ['org.springframework.roo:**'], }, ], }, @@ -675,7 +677,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring scala', - matchPackagePrefixes: ['org.springframework.scala:'], + matchPackageNames: ['org.springframework.scala:**'], }, ], }, @@ -684,7 +686,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring security', - matchPackagePrefixes: ['org.springframework.security:'], + matchPackageNames: ['org.springframework.security:**'], }, ], }, @@ -693,7 +695,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring session', - matchPackagePrefixes: ['org.springframework.session:'], + matchPackageNames: ['org.springframework.session:**'], }, ], }, @@ -702,7 +704,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring shell', - matchPackagePrefixes: ['org.springframework.shell:'], + matchPackageNames: ['org.springframework.shell:**'], }, ], }, @@ -711,7 +713,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring social', - matchPackagePrefixes: ['org.springframework.social:'], + matchPackageNames: ['org.springframework.social:**'], }, ], }, @@ -720,7 +722,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring statemachine', - matchPackagePrefixes: ['org.springframework.statemachine:'], + matchPackageNames: ['org.springframework.statemachine:**'], }, ], }, @@ -729,7 +731,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring webflow', - matchPackagePrefixes: ['org.springframework.webflow:'], + matchPackageNames: ['org.springframework.webflow:**'], }, ], }, @@ -738,7 +740,7 @@ const staticGroups = { packageRules: [ { groupName: 'spring ws', - matchPackagePrefixes: ['org.springframework.ws:'], + matchPackageNames: ['org.springframework.ws:**'], }, ], }, @@ -748,7 +750,7 @@ const staticGroups = { { groupName: 'symfony packages', groupSlug: 'symfony', - matchPackagePrefixes: ['symfony/'], + matchPackageNames: ['symfony/**'], }, ], }, diff --git a/lib/config/presets/internal/monorepo.ts b/lib/config/presets/internal/monorepo.ts index 1ce02c452d62b8..1e3982ec8fe382 100644 --- a/lib/config/presets/internal/monorepo.ts +++ b/lib/config/presets/internal/monorepo.ts @@ -591,13 +591,13 @@ for (const [name, value] of Object.entries(repoGroups)) { for (const [name, value] of Object.entries(orgGroups)) { presets[name] = { description: `${name} monorepo`, - matchSourceUrlPrefixes: toArray(value), + matchSourceUrls: toArray(value).map((prefix) => `${prefix}**`), }; } for (const [name, value] of Object.entries(patternGroups)) { presets[name] = { description: `${name} monorepo`, - matchPackagePatterns: toArray(value), + matchPackageNames: toArray(value).map((pattern) => `/${pattern}/`), }; } diff --git a/lib/config/presets/internal/packages.ts b/lib/config/presets/internal/packages.ts index daac49f540fa31..1056e6349745ee 100644 --- a/lib/config/presets/internal/packages.ts +++ b/lib/config/presets/internal/packages.ts @@ -14,11 +14,11 @@ export const presets: Record = { }, apollographql: { description: 'All packages published by Apollo GraphQL.', - matchSourceUrlPrefixes: ['https://github.com/apollographql/'], + matchSourceUrls: ['https://github.com/apollographql/**'], }, emberTemplateLint: { description: 'All ember-template-lint packages.', - matchPackagePrefixes: ['ember-template-lint'], + matchPackageNames: ['ember-template-lint**'], }, eslint: { description: 'All ESLint packages.', @@ -26,13 +26,10 @@ export const presets: Record = { '@types/eslint', 'babel-eslint', '@babel/eslint-parser', - ], - matchPackagePrefixes: [ - '@eslint/', - '@stylistic/eslint-plugin', - '@types/eslint__', - '@typescript-eslint/', - 'eslint', + '@eslint/**', + '@types/eslint__**', + '@typescript-eslint/**', + 'eslint**', ], }, gatsby: { @@ -42,8 +39,7 @@ export const presets: Record = { googleapis: { description: 'All `googleapis` packages.', matchDatasources: ['npm'], - matchPackageNames: ['google-auth-library'], - matchPackagePrefixes: ['@google-cloud/'], + matchPackageNames: ['google-auth-library', '@google-cloud/**'], }, jsTest: { description: 'JavaScript test packages.', @@ -77,17 +73,15 @@ export const presets: Record = { 'ts-auto-mock', 'ts-jest', 'vitest', - ], - matchPackagePrefixes: [ - '@testing-library', - '@types/testing-library__', - '@vitest', - 'chai', - 'jest', - 'mocha', - 'qunit', - 'should', - 'sinon', + '@testing-library**', + '@types/testing-library__**', + '@vitest**', + 'chai**', + 'jest**', + 'mocha**', + 'qunit**', + 'should**', + 'sinon**', ], }, linters: { @@ -103,7 +97,7 @@ export const presets: Record = { }, mapbox: { description: 'All Mapbox-related packages.', - matchPackagePrefixes: ['leaflet', 'mapbox'], + matchPackageNames: ['leaflet**', 'mapbox**'], }, phpLinters: { description: 'All PHP lint-related packages.', @@ -124,23 +118,21 @@ export const presets: Record = { 'phpspec/prophecy-phpunit', 'phpspec/phpspec', 'phpunit/phpunit', + 'pestphp/**', + 'php-mock/**', ], - matchPackagePrefixes: ['pestphp/', 'php-mock/'], }, postcss: { description: 'All PostCSS packages.', - matchPackageNames: ['postcss'], - matchPackagePrefixes: ['postcss-'], + matchPackageNames: ['postcss', 'postcss-**'], }, react: { description: 'All React packages.', - matchPackageNames: ['@types/react'], - matchPackagePrefixes: ['react'], + matchPackageNames: ['@types/react', 'react**'], }, stylelint: { description: 'All Stylelint packages.', - matchPackageNames: ['@stylistic/stylelint-plugin'], - matchPackagePrefixes: ['stylelint'], + matchPackageNames: ['stylelint**'], }, test: { description: 'Test packages.', @@ -148,8 +140,7 @@ export const presets: Record = { }, tslint: { description: 'All TSLint packages.', - matchPackageNames: ['codelyzer'], - matchPackagePatterns: ['\\btslint\\b'], + matchPackageNames: ['codelyzer', '/\\btslint\\b/'], }, unitTest: { description: 'All unit test packages.', @@ -158,6 +149,6 @@ export const presets: Record = { vite: { description: 'All Vite related packages', matchDatasources: ['npm'], - matchPackagePatterns: ['^vite$', 'vite-plugin', '@vitejs'], + matchPackageNames: ['vite', '**vite-plugin**', '@vitejs/**'], }, }; diff --git a/lib/config/presets/internal/replacements.ts b/lib/config/presets/internal/replacements.ts index 74335ad3acd1bd..cfce5aeb199cfe 100644 --- a/lib/config/presets/internal/replacements.ts +++ b/lib/config/presets/internal/replacements.ts @@ -128,10 +128,10 @@ export const presets: Record = { description: 'Replace `containerbase/(buildpack|base)` and `renovate/buildpack` with `ghcr.io/containerbase/base`.', matchDatasources: ['docker'], - matchPackagePatterns: [ - '^(?:docker\\.io/)?containerbase/(?:buildpack|base)$', - '^ghcr\\.io/containerbase/buildpack$', - '^(?:docker\\.io/)?renovate/buildpack$', + matchPackageNames: [ + '/^(?:docker\\.io/)?containerbase/(?:buildpack|base)$/', + '/^ghcr\\.io/containerbase/buildpack$/', + '/^(?:docker\\.io/)?renovate/buildpack$/', ], replacementName: 'ghcr.io/containerbase/base', }, @@ -139,8 +139,8 @@ export const presets: Record = { description: 'Replace `containerbase/node` and `renovate/node` with `ghcr.io/containerbase/node`.', matchDatasources: ['docker'], - matchPackagePatterns: [ - '^(?:docker\\.io/)?(?:containerbase|renovate)/node$', + matchPackageNames: [ + '/^(?:docker\\.io/)?(?:containerbase|renovate)/node$/', ], replacementName: 'ghcr.io/containerbase/node', }, @@ -148,8 +148,8 @@ export const presets: Record = { description: 'Replace `containerbase/sidecar` and `renovate/sidecar` with `ghcr.io/containerbase/sidecar`.', matchDatasources: ['docker'], - matchPackagePatterns: [ - '^(?:docker\\.io/)?(?:containerbase|renovate)/sidecar$', + matchPackageNames: [ + '/^(?:docker\\.io/)?(?:containerbase|renovate)/sidecar$/', ], replacementName: 'ghcr.io/containerbase/sidecar', }, @@ -164,8 +164,10 @@ export const presets: Record = { description: 'Replace `renovate` `slim` docker tag with `latest`.', matchCurrentValue: '/^slim$/', matchDatasources: ['docker'], - matchPackageNames: ['ghcr.io/renovatebot/renovate'], - matchPackagePatterns: ['^(?:docker\\.io/)?renovate/renovate$'], + matchPackageNames: [ + 'ghcr.io/renovatebot/renovate', + '/^(?:docker\\.io/)?renovate/renovate$/', + ], replacementVersion: 'latest', }, { @@ -173,8 +175,10 @@ export const presets: Record = { extractVersion: '^(?.+)-slim$', matchCurrentValue: '/-slim$/', matchDatasources: ['docker'], - matchPackageNames: ['ghcr.io/renovatebot/renovate'], - matchPackagePatterns: ['^(?:docker\\.io/)?renovate/renovate$'], + matchPackageNames: [ + 'ghcr.io/renovatebot/renovate', + '/^(?:docker\\.io/)?renovate/renovate$/s', + ], versioning: 'semver', }, ], @@ -675,7 +679,7 @@ export const presets: Record = { packageRules: [ { matchDatasources: ['docker'], - matchPackagePatterns: ['^k8s\\.gcr\\.io/.+$'], + matchPackageNames: ['/^k8s\\.gcr\\.io/.+$/'], replacementNameTemplate: "{{{replace 'k8s\\.gcr\\.io/' 'registry.k8s.io/' packageName}}}", }, @@ -959,9 +963,9 @@ export const presets: Record = { 'The `zap-stable` image has moved to the `zaproxy` organization.', matchCurrentVersion: '>=2.0.0 <2.14.0', matchDatasources: ['docker'], - matchPackagePatterns: [ - '^(?:docker\\.io/)?owasp/zap2docker-stable$', - '^(?:docker\\.io/)?softwaresecurityproject/zap-stable$', + matchPackageNames: [ + '/^(?:docker\\.io/)?owasp/zap2docker-stable$/', + '/^(?:docker\\.io/)?softwaresecurityproject/zap-stable$/', ], replacementName: 'zaproxy/zap-stable', replacementVersion: '2.14.0', @@ -971,9 +975,9 @@ export const presets: Record = { 'The `zap-bare` image has moved to the `zaproxy` organization.', matchCurrentVersion: '>=2.0.0 <2.14.0', matchDatasources: ['docker'], - matchPackagePatterns: [ - '^(?:docker\\.io/)?owasp/zap2docker-bare$', - '^(?:docker\\.io/)?softwaresecurityproject/zap-bare$', + matchPackageNames: [ + '/^(?:docker\\.io/)?owasp/zap2docker-bare$/', + '/^(?:docker\\.io/)?softwaresecurityproject/zap-bare$/', ], replacementName: 'zaproxy/zap-bare', replacementVersion: '2.14.0', diff --git a/lib/config/presets/internal/security.ts b/lib/config/presets/internal/security.ts index a5e72a45e21eec..00998d05ba8233 100644 --- a/lib/config/presets/internal/security.ts +++ b/lib/config/presets/internal/security.ts @@ -5,7 +5,7 @@ export const presets: Record = { description: 'Show OpenSSF badge on pull requests.', packageRules: [ { - matchSourceUrlPrefixes: ['https://github.com/'], + matchSourceUrls: ['https://github.com/**'], prBodyDefinitions: { OpenSSF: '[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/{{sourceRepo}}/badge)](https://securityscorecards.dev/viewer/?uri=github.com/{{sourceRepo}})', diff --git a/lib/config/presets/internal/workarounds.ts b/lib/config/presets/internal/workarounds.ts index 158515222e43b0..8c4441ecd3e2b9 100644 --- a/lib/config/presets/internal/workarounds.ts +++ b/lib/config/presets/internal/workarounds.ts @@ -32,10 +32,10 @@ export const presets: Record = { matchCurrentValue: '/^(?\\d+)(?:\\.(?\\d+)(?:\\.(?\\d+))?)?-(?.+)-(?\\d+)(?:-r(?\\d+))?$/', matchDatasources: ['docker'], - matchPackagePrefixes: [ - 'bitnami/', - 'docker.io/bitnami/', - 'gcr.io/bitnami-containers/', + matchPackageNames: [ + 'bitnami/**', + 'docker.io/bitnami/**', + 'gcr.io/bitnami-containers/**', ], versioning: 'regex:^(?\\d+)(?:\\.(?\\d+)(?:\\.(?\\d+))?)?(:?-(?.+)-(?\\d+)(?:-r(?\\d+))?)?$', @@ -49,8 +49,8 @@ export const presets: Record = { description: 'Use node versioning for `(containerbase|renovate)/node` images', matchDatasources: ['docker'], - matchPackagePatterns: [ - '^(?:(?:docker|ghcr)\\.io/)?(?:containerbase|renovate)/node$', + matchPackageNames: [ + '/^(?:(?:docker|ghcr)\\.io/)?(?:containerbase|renovate)/node$/', ], versioning: 'node', }, @@ -100,7 +100,7 @@ export const presets: Record = { { allowedVersions: `!/^1\\.0-\\d+-[a-fA-F0-9]{7}$/`, matchManagers: ['sbt'], - matchPackagePrefixes: ['org.http4s:'], + matchPackageNames: ['org.http4s:**'], }, ], }, @@ -142,11 +142,9 @@ export const presets: Record = { 'java', 'java-jre', 'sapmachine', - ], - matchPackagePatterns: [ - '^azul/zulu-openjdk', - '^bellsoft/liberica-openj(dk|re)-', - '^cimg/openjdk', + '/^azul/zulu-openjdk/', + '/^bellsoft/liberica-openj(dk|re)-/', + '/^cimg/openjdk/', ], versioning: 'regex:^(?\\d+)?(\\.(?\\d+))?(\\.(?\\d+))?([\\._+](?(\\d\\.?)+)(LTS)?)?(-(?.*))?$', @@ -176,7 +174,7 @@ export const presets: Record = { { allowedVersions: '!/^200\\d{5}(\\.\\d+)?/', matchDatasources: ['maven', 'sbt-package'], - matchPackagePrefixes: ['commons-'], + matchPackageNames: ['commons-**'], }, ], }, @@ -213,18 +211,16 @@ export const presets: Record = { 'registry.access.redhat.com/rhel-atomic', 'registry.access.redhat.com/rhel-init', 'registry.access.redhat.com/rhel-minimal', - ], - matchPackagePrefixes: [ - 'registry.access.redhat.com/rhceph/', - 'registry.access.redhat.com/rhgs3/', - 'registry.access.redhat.com/rhel7', - 'registry.access.redhat.com/rhel8/', - 'registry.access.redhat.com/rhel9/', - 'registry.access.redhat.com/rhscl/', - 'registry.access.redhat.com/ubi7', - 'registry.access.redhat.com/ubi8', - 'registry.access.redhat.com/ubi9', - 'redhat/', + 'registry.access.redhat.com/rhceph/**', + 'registry.access.redhat.com/rhgs3/**', + 'registry.access.redhat.com/rhel7**', + 'registry.access.redhat.com/rhel8/**', + 'registry.access.redhat.com/rhel9/**', + 'registry.access.redhat.com/rhscl/**', + 'registry.access.redhat.com/ubi7**', + 'registry.access.redhat.com/ubi8**', + 'registry.access.redhat.com/ubi9**', + 'redhat/**', ], versioning: 'redhat', }, diff --git a/lib/config/presets/npm/index.spec.ts b/lib/config/presets/npm/index.spec.ts index 5e9fb095354015..35f90af0f13a4d 100644 --- a/lib/config/presets/npm/index.spec.ts +++ b/lib/config/presets/npm/index.spec.ts @@ -7,10 +7,6 @@ describe('config/presets/npm/index', () => { GlobalConfig.reset(); }); - afterEach(() => { - delete process.env.RENOVATE_CACHE_NPM_MINUTES; - }); - it('should throw if no package', async () => { httpMock.scope('https://registry.npmjs.org').get('/nopackage').reply(404); await expect( diff --git a/lib/config/types.ts b/lib/config/types.ts index f62e4d2484e458..c5c53c89cf1d93 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -1,4 +1,3 @@ -import type { LogLevel } from 'bunyan'; import type { PlatformId } from '../constants'; import type { LogLevelRemap } from '../logger/types'; import type { CustomManager } from '../modules/manager/custom/types'; @@ -27,6 +26,7 @@ export interface GroupConfig extends Record { } export type RecreateWhen = 'auto' | 'never' | 'always'; +export type PlatformCommitOptions = 'auto' | 'disabled' | 'enabled'; // TODO: Proper typings export interface RenovateSharedConfig { $schema?: string; @@ -70,7 +70,7 @@ export interface RenovateSharedConfig { milestone?: number; npmrc?: string; npmrcMerge?: boolean; - platformCommit?: boolean; + platformCommit?: PlatformCommitOptions; postUpgradeTasks?: PostUpgradeTasks; prBodyColumns?: string[]; prBodyDefinitions?: Record; @@ -114,8 +114,6 @@ export interface GlobalOnlyConfig { gitNoVerify?: GitNoVerifyOption[]; gitPrivateKey?: string; globalExtends?: string[]; - logFile?: string; - logFileLevel?: LogLevel; mergeConfidenceDatasources?: string[]; mergeConfidenceEndpoint?: string; platform?: PlatformId; @@ -162,6 +160,7 @@ export interface RepoGlobalConfig { presetCachePersistence?: boolean; privateKey?: string; privateKeyOld?: string; + experimentalFlags?: string[]; httpCacheTtlDays?: number; autodiscoverRepoSort?: RepoSortMethod; autodiscoverRepoOrder?: SortMethod; @@ -176,7 +175,7 @@ export interface LegacyAdminConfig { onboarding?: boolean; onboardingBranch?: string; onboardingCommitMessage?: string; - onboardingNoDeps?: boolean; + onboardingNoDeps?: 'auto' | 'enabled' | 'disabled'; onboardingRebaseCheckbox?: boolean; onboardingPrTitle?: string; onboardingConfig?: RenovateSharedConfig; @@ -358,13 +357,6 @@ export interface PackageRule UpdateConfig, Record { description?: string | string[]; - excludeDepNames?: string[]; - excludeDepPatterns?: string[]; - excludeDepPrefixes?: string[]; - excludePackageNames?: string[]; - excludePackagePatterns?: string[]; - excludePackagePrefixes?: string[]; - excludeRepositories?: string[]; isVulnerabilityAlert?: boolean; matchBaseBranches?: string[]; matchCategories?: string[]; @@ -374,17 +366,12 @@ export interface PackageRule matchCurrentVersion?: string; matchDatasources?: string[]; matchDepNames?: string[]; - matchDepPatterns?: string[]; - matchDepPrefixes?: string[]; matchDepTypes?: string[]; matchFileNames?: string[]; matchManagers?: string[]; matchNewValue?: string; matchPackageNames?: string[]; - matchPackagePatterns?: string[]; - matchPackagePrefixes?: string[]; matchRepositories?: string[]; - matchSourceUrlPrefixes?: string[]; matchSourceUrls?: string[]; matchUpdateTypes?: UpdateType[]; registryUrls?: string[] | null; diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index ee8e4d5a5fa040..ac472f45d0ba1d 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -101,6 +101,25 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); }); + it('does not warn for valid platformConfig', async () => { + const config = { + platformConfig: 'auto', + }; + const { warnings } = await configValidation.validateConfig( + 'repo', + config, + ); + expect(warnings).toHaveLength(0); + }); + + it('warns for invalid platformConfig', async () => { + const config = { + platformConfig: 'invalid', + }; + const { errors } = await configValidation.validateConfig('repo', config); + expect(errors).toHaveLength(1); + }); + it('catches invalid templates', async () => { const config = { commitMessage: '{{{something}}', @@ -369,8 +388,11 @@ describe('config/validation', () => { timezone: 'Asia/Singapore', packageRules: [ { - matchPackagePatterns: ['*'], - excludePackagePatterns: ['abc ([a-z]+) ([a-z]+))'], + matchPackageNames: [ + '*', + '/abc ([a-z]+) ([a-z]+))/', + '!/abc ([a-z]+) ([a-z]+))/', + ], enabled: true, }, ], @@ -384,7 +406,7 @@ describe('config/validation', () => { config, ); expect(warnings).toHaveLength(0); - expect(errors).toHaveLength(3); + expect(errors).toHaveLength(4); expect(errors).toMatchSnapshot(); }); @@ -476,19 +498,13 @@ describe('config/validation', () => { lockFileMaintenance: false as any, extends: [':timezone(Europe/Brussel)'], packageRules: [ - { - excludePackageNames: ['foo'], - enabled: true, - }, { foo: 1, }, 'what?' as any, { - matchDepPatterns: 'abc ([a-z]+) ([a-z]+))', - matchPackagePatterns: 'abc ([a-z]+) ([a-z]+))', - excludeDepPatterns: ['abc ([a-z]+) ([a-z]+))'], - excludePackagePatterns: ['abc ([a-z]+) ([a-z]+))'], + matchPackageNames: '/abc ([a-z]+) ([a-z]+))/', + matchDepNames: ['abc ([a-z]+) ([a-z]+))'], enabled: false, }, ], @@ -500,7 +516,7 @@ describe('config/validation', () => { ); expect(warnings).toHaveLength(1); expect(errors).toMatchSnapshot(); - expect(errors).toHaveLength(15); + expect(errors).toHaveLength(12); }); it('selectors outside packageRules array trigger errors', async () => { @@ -1044,9 +1060,7 @@ describe('config/validation', () => { it('warns if only selectors in packageRules', async () => { const config = { - packageRules: [ - { matchDepTypes: ['foo'], excludePackageNames: ['bar'] }, - ], + packageRules: [{ matchDepTypes: ['foo'], matchPackageNames: ['bar'] }], }; const { warnings, errors } = await configValidation.validateConfig( 'repo', @@ -1346,6 +1360,27 @@ describe('config/validation', () => { }); describe('validateConfig() -> globaOnly options', () => { + it('returns errors for invalid options', async () => { + const config = { + logFile: 'something', + logFileLevel: 'DEBUG', + }; + const { errors } = await configValidation.validateConfig( + 'global', + config, + ); + expect(errors).toMatchObject([ + { + message: 'Invalid configuration option: logFile', + topic: 'Configuration Error', + }, + { + message: 'Invalid configuration option: logFileLevel', + topic: 'Configuration Error', + }, + ]); + }); + it('validates hostRules.headers', async () => { const config = { hostRules: [ @@ -1459,6 +1494,23 @@ describe('config/validation', () => { }); describe('validate globalOptions()', () => { + it('binarySource', async () => { + const config = { + binarySource: 'invalid' as never, + }; + const { warnings } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toEqual([ + { + message: + 'Invalid value `invalid` for `binarySource`. The allowed values are docker, global, install, hermit.', + topic: 'Configuration Error', + }, + ]); + }); + describe('validates string type options', () => { it('binarySource', async () => { const config = { @@ -1888,4 +1940,42 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); }); }); + + describe('validate experimental flags', () => { + beforeEach(() => { + GlobalConfig.reset(); + }); + + it('warns if invalid flag found', async () => { + const config = { + experimentalFlags: ['invalidtag', 10], + }; + const { warnings } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toEqual([ + { + topic: 'Configuration Error', + message: + 'Experimental flags can only be of type string. Found invalid type number', + }, + { + topic: 'Configuration Error', + message: 'Invalid flag `invalidtag` found in `experimentalFlags`.', + }, + ]); + }); + + it('disableDockerHubTags', async () => { + const config = { + experimentalFlags: ['disableDockerHubTags'], + }; + const { warnings } = await configValidation.validateConfig( + 'global', + config, + ); + expect(warnings).toBeEmptyArray(); + }); + }); }); diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 15b87c3b144022..078d17e7d19427 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -1,4 +1,5 @@ import is from '@sindresorhus/is'; +import { allowedExperimentalFlags } from '../constants/experimental-flags'; import { logger } from '../logger'; import { allManagersList, getManagerList } from '../modules/manager'; import { isCustomManager } from '../modules/manager/custom'; @@ -431,21 +432,9 @@ export async function validateConfig( 'matchDatasources', 'matchDepTypes', 'matchDepNames', - 'matchDepPatterns', - 'matchDepPrefixes', 'matchPackageNames', - 'matchPackagePatterns', - 'matchPackagePrefixes', - 'excludeDepNames', - 'excludeDepPatterns', - 'excludeDepPrefixes', - 'excludePackageNames', - 'excludePackagePatterns', - 'excludePackagePrefixes', - 'excludeRepositories', 'matchCurrentValue', 'matchCurrentVersion', - 'matchSourceUrlPrefixes', 'matchSourceUrls', 'matchUpdateTypes', 'matchConfidence', @@ -603,18 +592,14 @@ export async function validateConfig( } } } - if ( - [ - 'matchPackagePatterns', - 'excludePackagePatterns', - 'matchDepPatterns', - 'excludeDepPatterns', - ].includes(key) - ) { + if (['matchPackageNames', 'matchDepNames'].includes(key)) { + const startPattern = regEx(/!?\//); + const endPattern = regEx(/\/g?i?$/); for (const pattern of val as string[]) { - if (pattern !== '*') { + if (startPattern.test(pattern) && endPattern.test(pattern)) { try { - regEx(pattern); + // regEx isn't aware of our !/ prefix but can handle the suffix + regEx(pattern.replace(startPattern, '/')); } catch (e) { errors.push({ topic: 'Configuration Error', @@ -920,6 +905,13 @@ async function validateGlobalConfig( currentPath: string | undefined, config: RenovateConfig, ): Promise { + // istanbul ignore if + if (getDeprecationMessage(key)) { + warnings.push({ + topic: 'Deprecation Warning', + message: getDeprecationMessage(key)!, + }); + } if (val !== null) { if (type === 'string') { if (is.string(val)) { @@ -1010,7 +1002,18 @@ async function validateGlobalConfig( }), ); } - if (key === 'gitNoVerify') { + if (key === 'experimentalFlags') { + for (const flag of val) { + if (is.string(flag)) { + validateExperimentalFlag(flag, warnings, currentPath); + } else { + warnings.push({ + topic: 'Configuration Error', + message: `Experimental flags can only be of type string. Found invalid type ${typeof flag}`, + }); + } + } + } else if (key === 'gitNoVerify') { const allowedValues = ['commit', 'push']; for (const value of val as string[]) { if (!allowedValues.includes(value)) { @@ -1097,3 +1100,17 @@ function isFalseGlobal(optionName: string, parentPath?: string): boolean { return false; } + +function validateExperimentalFlag( + flagName: string, + warnings: ValidationMessage[], + currentPath: string | undefined, +): void { + if (!allowedExperimentalFlags.has(flagName)) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid flag \`${flagName}\` found in \`${currentPath}\`.`, + }); + return; + } +} diff --git a/lib/constants/experimental-flags.ts b/lib/constants/experimental-flags.ts new file mode 100644 index 00000000000000..f5b4f19f25e728 --- /dev/null +++ b/lib/constants/experimental-flags.ts @@ -0,0 +1,9 @@ +export const allowedExperimentalFlags = new Set([ + 'disableDockerHubTags', + 'execGpidHandle', + 'noMavenPomCheck', + 'nugetDownloadNupkgs', + 'repoCacheForceLocal', + 'yarnProxy', + 'useOpenpgp', +]); diff --git a/lib/logger/index.ts b/lib/logger/index.ts index 54cb4ebf4e497a..504e9b1a2c97e8 100644 --- a/lib/logger/index.ts +++ b/lib/logger/index.ts @@ -1,6 +1,8 @@ import is from '@sindresorhus/is'; import * as bunyan from 'bunyan'; +import fs from 'fs-extra'; import { nanoid } from 'nanoid'; +import upath from 'upath'; import cmdSerializer from './cmd-serializer'; import configSerializer from './config-serializer'; import errSerializer from './err-serializer'; @@ -20,16 +22,13 @@ if (is.string(process.env.LOG_LEVEL)) { process.env.LOG_LEVEL = process.env.LOG_LEVEL.toLowerCase().trim(); } -validateLogLevel(process.env.LOG_LEVEL); const stdout: bunyan.Stream = { name: 'stdout', - level: - (process.env.LOG_LEVEL as bunyan.LogLevel) || - /* istanbul ignore next: not testable */ 'info', + level: validateLogLevel(process.env.LOG_LEVEL, 'info'), stream: process.stdout, }; -// istanbul ignore else: not testable +// istanbul ignore if: not testable if (process.env.LOG_FORMAT !== 'json') { // TODO: typings (#9615) const prettyStdOut = new RenovateStream() as any; @@ -123,6 +122,19 @@ loggerLevels.forEach((loggerLevel) => { logger.once[loggerLevel] = logOnceFn as never; }); +// istanbul ignore if: not easily testable +if (is.string(process.env.LOG_FILE)) { + // ensure log file directory exists + const directoryName = upath.dirname(process.env.LOG_FILE); + fs.ensureDirSync(directoryName); + + addStream({ + name: 'logfile', + path: process.env.LOG_FILE, + level: validateLogLevel(process.env.LOG_FILE_LEVEL, 'debug'), + }); +} + export function setContext(value: string): void { logContext = value; } diff --git a/lib/logger/utils.spec.ts b/lib/logger/utils.spec.ts index 51b3ac222616bf..2073782a91c1c1 100644 --- a/lib/logger/utils.spec.ts +++ b/lib/logger/utils.spec.ts @@ -11,11 +11,13 @@ describe('logger/utils', () => { }); it('checks for valid log levels', () => { - expect(validateLogLevel(undefined)).toBeUndefined(); - expect(validateLogLevel('warn')).toBeUndefined(); - expect(validateLogLevel('debug')).toBeUndefined(); - expect(validateLogLevel('trace')).toBeUndefined(); - expect(validateLogLevel('info')).toBeUndefined(); + expect(validateLogLevel(undefined, 'info')).toBe('info'); + expect(validateLogLevel('warn', 'info')).toBe('warn'); + expect(validateLogLevel('debug', 'info')).toBe('debug'); + expect(validateLogLevel('trace', 'info')).toBe('trace'); + expect(validateLogLevel('info', 'info')).toBe('info'); + expect(validateLogLevel('error', 'info')).toBe('error'); + expect(validateLogLevel('fatal', 'info')).toBe('fatal'); }); it.each` @@ -32,7 +34,7 @@ describe('logger/utils', () => { throw new Error(`process.exit: ${number}`); }); expect(() => { - validateLogLevel(input); + validateLogLevel(input, 'info'); }).toThrow(); expect(mockExit).toHaveBeenCalledWith(1); }); diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index dc49caa7f957ea..5fc70628769b09 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -277,13 +277,16 @@ export function withSanitizer(streamConfig: bunyan.Stream): bunyan.Stream { } /** - * A function that terminates exeution if the log level that was entered is + * A function that terminates execution if the log level that was entered is * not a valid value for the Bunyan logger. * @param logLevelToCheck - * @returns returns undefined when the logLevelToCheck is valid. Else it stops execution. + * @returns returns the logLevel when the logLevelToCheck is valid or the defaultLevel passed as argument when it is undefined. Else it stops execution. */ -export function validateLogLevel(logLevelToCheck: string | undefined): void { - const allowedValues: bunyan.LogLevel[] = [ +export function validateLogLevel( + logLevelToCheck: string | undefined, + defaultLevel: bunyan.LogLevelString, +): bunyan.LogLevelString { + const allowedValues: bunyan.LogLevelString[] = [ 'trace', 'debug', 'info', @@ -291,13 +294,14 @@ export function validateLogLevel(logLevelToCheck: string | undefined): void { 'error', 'fatal', ]; + if ( is.undefined(logLevelToCheck) || (is.string(logLevelToCheck) && - allowedValues.includes(logLevelToCheck as bunyan.LogLevel)) + allowedValues.includes(logLevelToCheck as bunyan.LogLevelString)) ) { // log level is in the allowed values or its undefined - return; + return (logLevelToCheck as bunyan.LogLevelString) ?? defaultLevel; } const logger = bunyan.createLogger({ diff --git a/lib/modules/datasource/docker/index.spec.ts b/lib/modules/datasource/docker/index.spec.ts index e5805c7ed906e0..d6f025a787591d 100644 --- a/lib/modules/datasource/docker/index.spec.ts +++ b/lib/modules/datasource/docker/index.spec.ts @@ -10,6 +10,7 @@ import { getDigest, getPkgReleases } from '..'; import { range } from '../../../../lib/util/range'; import * as httpMock from '../../../../test/http-mock'; import { logger, mocked } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages'; import * as _hostRules from '../../../util/host-rules'; import { DockerDatasource } from '.'; @@ -47,27 +48,11 @@ describe('modules/datasource/docker/index', () => { password: 'some-password', }); hostRules.hosts.mockReturnValue([]); + GlobalConfig.reset(); delete process.env.RENOVATE_X_DOCKER_MAX_PAGES; - delete process.env.RENOVATE_X_DOCKER_HUB_TAGS; }); describe('getDigest', () => { - it('returns null if no token', async () => { - httpMock - .scope(baseUrl) - .get('/', undefined, { badheaders: ['authorization'] }) - .reply(200, '', {}) - .head('/library/some-dep/manifests/some-new-value', undefined, { - badheaders: ['authorization'], - }) - .reply(401); - const res = await getDigest( - { datasource: 'docker', packageName: 'some-dep' }, - 'some-new-value', - ); - expect(res).toBeNull(); - }); - it('returns null if errored', async () => { httpMock .scope(baseUrl) @@ -1317,6 +1302,7 @@ describe('modules/datasource/docker/index', () => { describe('getReleases', () => { it('returns null if no token', async () => { + GlobalConfig.set({ experimentalFlags: ['disableDockerHubTags'] }); httpMock .scope(baseUrl) .get('/library/node/tags/list?n=10000') @@ -1363,6 +1349,7 @@ describe('modules/datasource/docker/index', () => { }); it('uses custom max pages', async () => { + GlobalConfig.set({ experimentalFlags: ['disableDockerHubTags'] }); process.env.RENOVATE_X_DOCKER_MAX_PAGES = '2'; httpMock .scope(baseUrl) @@ -1870,7 +1857,6 @@ describe('modules/datasource/docker/index', () => { }); it('Uses Docker Hub tags for registry-1.docker.io', async () => { - process.env.RENOVATE_X_DOCKER_HUB_TAGS = 'true'; httpMock .scope(dockerHubUrl) .get('/library/node/tags?page_size=1000&ordering=last_updated') @@ -1915,7 +1901,6 @@ describe('modules/datasource/docker/index', () => { }); it('adds library/ prefix for Docker Hub (implicit)', async () => { - process.env.RENOVATE_X_DOCKER_HUB_TAGS = 'true'; const tags = ['1.0.0']; httpMock .scope(dockerHubUrl) @@ -1944,7 +1929,6 @@ describe('modules/datasource/docker/index', () => { }); it('adds library/ prefix for Docker Hub (explicit)', async () => { - process.env.RENOVATE_X_DOCKER_HUB_TAGS = 'true'; httpMock .scope(dockerHubUrl) .get('/library/node/tags?page_size=1000&ordering=last_updated') @@ -2015,6 +1999,7 @@ describe('modules/datasource/docker/index', () => { }); it('returns null on error', async () => { + GlobalConfig.set({ experimentalFlags: ['disableDockerHubTags'] }); httpMock .scope(baseUrl) .get('/my/node/tags/list?n=10000') @@ -2029,6 +2014,7 @@ describe('modules/datasource/docker/index', () => { }); it('strips trailing slash from registry', async () => { + GlobalConfig.set({ experimentalFlags: ['disableDockerHubTags'] }); httpMock .scope(baseUrl) .get('/my/node/tags/list?n=10000') @@ -2055,6 +2041,7 @@ describe('modules/datasource/docker/index', () => { }); it('returns null if no auth', async () => { + GlobalConfig.set({ experimentalFlags: ['disableDockerHubTags'] }); hostRules.find.mockReturnValue({}); httpMock .scope(baseUrl) diff --git a/lib/modules/datasource/docker/index.ts b/lib/modules/datasource/docker/index.ts index c0266ac7a09a03..84593489dd4c8f 100644 --- a/lib/modules/datasource/docker/index.ts +++ b/lib/modules/datasource/docker/index.ts @@ -1,4 +1,5 @@ import is from '@sindresorhus/is'; +import { GlobalConfig } from '../../../config/global'; import { PAGE_NOT_FOUND_ERROR } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; @@ -1064,9 +1065,11 @@ export class DockerDatasource extends Datasource { 'dockerhub-error' as const, ).catch(getTags); + const disableDockerHubTags = GlobalConfig.getExperimentalFlag( + 'disableDockerHubTags', + ); const tagsResult = - registryHost === 'https://index.docker.io' && - process.env.RENOVATE_X_DOCKER_HUB_TAGS + registryHost === 'https://index.docker.io' && !disableDockerHubTags ? getDockerHubTags() : getTags(); diff --git a/lib/modules/datasource/maven/index.spec.ts b/lib/modules/datasource/maven/index.spec.ts index d51f6f1f025a1d..885d95ccd2bbac 100644 --- a/lib/modules/datasource/maven/index.spec.ts +++ b/lib/modules/datasource/maven/index.spec.ts @@ -3,6 +3,7 @@ import { ReleaseResult, getPkgReleases } from '..'; import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; import { mocked } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages'; import * as hostRules from '../../../util/host-rules'; import { id as versioning } from '../../versioning/maven'; @@ -189,7 +190,7 @@ describe('modules/datasource/maven/index', () => { afterEach(() => { hostRules.clear(); - delete process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK; + GlobalConfig.reset(); }); it('returns null when metadata is not found', async () => { @@ -252,7 +253,7 @@ describe('modules/datasource/maven/index', () => { }); it('returns html-based releases', async () => { - process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK = 'true'; + GlobalConfig.set({ experimentalFlags: ['noMavenPomCheck'] }); mockGenericPackage({ latest: '2.0.0', @@ -402,7 +403,7 @@ describe('modules/datasource/maven/index', () => { }); it('removes authentication header after redirect', async () => { - process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK = 'true'; + GlobalConfig.set({ experimentalFlags: ['noMavenPomCheck'] }); const frontendHost = 'frontend_for_private_s3_repository'; const frontendUrl = `https://${frontendHost}/maven2`; diff --git a/lib/modules/datasource/maven/index.ts b/lib/modules/datasource/maven/index.ts index 3232823257c48e..b2f5bd83a4ffe6 100644 --- a/lib/modules/datasource/maven/index.ts +++ b/lib/modules/datasource/maven/index.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { DateTime } from 'luxon'; import type { XmlDocument } from 'xmldoc'; +import { GlobalConfig } from '../../../config/global'; import { logger } from '../../../logger'; import * as packageCache from '../../../util/cache/package'; import { filterMap } from '../../../util/filter-map'; @@ -213,7 +214,7 @@ export class MavenDatasource extends Datasource { ): Promise { const releaseMap = { ...inputReleaseMap }; - if (process.env.RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK) { + if (GlobalConfig.getExperimentalFlag('noMavenPomCheck')) { return releaseMap; } diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts index cc8024ebdd0e80..cd8ea585ec259f 100644 --- a/lib/modules/datasource/npm/get.ts +++ b/lib/modules/datasource/npm/get.ts @@ -107,9 +107,7 @@ export async function getDependency( delete cachedResult.cacheData; } } - const cacheMinutes = process.env.RENOVATE_CACHE_NPM_MINUTES - ? parseInt(process.env.RENOVATE_CACHE_NPM_MINUTES, 10) - : 15; + const cacheMinutes = 15; const softExpireAt = DateTime.local().plus({ minutes: cacheMinutes }).toISO(); let cacheHardTtlMinutes = GlobalConfig.get('cacheHardTtlMinutes'); if ( diff --git a/lib/modules/datasource/npm/index.spec.ts b/lib/modules/datasource/npm/index.spec.ts index 9e249734afedff..a0c273d0adbec8 100644 --- a/lib/modules/datasource/npm/index.spec.ts +++ b/lib/modules/datasource/npm/index.spec.ts @@ -40,10 +40,6 @@ describe('modules/datasource/npm/index', () => { }; }); - afterEach(() => { - delete process.env.RENOVATE_CACHE_NPM_MINUTES; - }); - it('should return null for no versions', async () => { const missingVersions = { ...npmResponse }; missingVersions.versions = {}; @@ -329,7 +325,6 @@ describe('modules/datasource/npm/index', () => { .get('/foobar') .reply(200, npmResponse); process.env.REGISTRY = 'https://registry.from-env.com'; - process.env.RENOVATE_CACHE_NPM_MINUTES = '15'; GlobalConfig.set({ exposeAllEnv: true }); const npmrc = 'registry=${REGISTRY}'; diff --git a/lib/modules/datasource/npm/npmrc.spec.ts b/lib/modules/datasource/npm/npmrc.spec.ts index 707270507eb05b..21cc3b35b61e66 100644 --- a/lib/modules/datasource/npm/npmrc.spec.ts +++ b/lib/modules/datasource/npm/npmrc.spec.ts @@ -136,8 +136,8 @@ describe('modules/datasource/npm/npmrc', () => { "matchDatasources": [ "npm", ], - "matchPackagePrefixes": [ - "@fontawesome/", + "matchPackageNames": [ + "@fontawesome/**", ], "registryUrls": [ "https://npm.fontawesome.com/", diff --git a/lib/modules/datasource/npm/npmrc.ts b/lib/modules/datasource/npm/npmrc.ts index 73b2e1eef52896..72291ad2e6bbc0 100644 --- a/lib/modules/datasource/npm/npmrc.ts +++ b/lib/modules/datasource/npm/npmrc.ts @@ -111,7 +111,7 @@ export function convertNpmrcToRules(npmrc: Record): NpmrcRules { if (isHttpUrl(value)) { rules.packageRules?.push({ matchDatasources, - matchPackagePrefixes: [scope + '/'], + matchPackageNames: [`${scope}/**`], registryUrls: [value], }); } else { @@ -168,10 +168,10 @@ export function setNpmrc(input?: string): void { export function resolveRegistryUrl(packageName: string): string { let registryUrl = defaultRegistryUrls[0]; for (const rule of packageRules) { - const { matchPackagePrefixes, registryUrls } = rule; + const { matchPackageNames, registryUrls } = rule; if ( - !matchPackagePrefixes || - packageName.startsWith(matchPackagePrefixes[0]) + !matchPackageNames || + packageName.startsWith(matchPackageNames[0].replace(regEx(/\*\*$/), '')) ) { // TODO: fix types #22198 registryUrl = registryUrls![0]; diff --git a/lib/modules/datasource/nuget/index.spec.ts b/lib/modules/datasource/nuget/index.spec.ts index ce25cf7722b9c0..ae10141543c6c8 100644 --- a/lib/modules/datasource/nuget/index.spec.ts +++ b/lib/modules/datasource/nuget/index.spec.ts @@ -317,12 +317,8 @@ describe('modules/datasource/nuget/index', () => { beforeEach(() => { GlobalConfig.set({ cacheDir: join('/tmp/cache'), + experimentalFlags: ['nugetDownloadNupkgs'], }); - process.env.RENOVATE_X_NUGET_DOWNLOAD_NUPKGS = 'true'; - }); - - afterEach(() => { - delete process.env.RENOVATE_X_NUGET_DOWNLOAD_NUPKGS; }); it('can determine source URL from nupkg when PackageBaseAddress is missing', async () => { diff --git a/lib/modules/datasource/nuget/v3.ts b/lib/modules/datasource/nuget/v3.ts index 1d0dc587c01d3a..bcee4c5b6ecb24 100644 --- a/lib/modules/datasource/nuget/v3.ts +++ b/lib/modules/datasource/nuget/v3.ts @@ -3,6 +3,7 @@ import extract from 'extract-zip'; import semver from 'semver'; import upath from 'upath'; import { XmlDocument } from 'xmldoc'; +import { GlobalConfig } from '../../../config/global'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as packageCache from '../../../util/cache/package'; @@ -278,8 +279,8 @@ export class NugetV3Api { nupkgUrl: string, ): Promise { // istanbul ignore if: experimental feature - if (!process.env.RENOVATE_X_NUGET_DOWNLOAD_NUPKGS) { - logger.once.debug('RENOVATE_X_NUGET_DOWNLOAD_NUPKGS is not set'); + if (!GlobalConfig.getExperimentalFlag('nugetDownloadNupkgs')) { + logger.once.debug('Flag nugetDownloadNupkgs is not set'); return null; } const cacheDir = await ensureCacheDir('nuget'); diff --git a/lib/modules/manager/npm/post-update/index.spec.ts b/lib/modules/manager/npm/post-update/index.spec.ts index 600afddf124aef..9420606cdf40af 100644 --- a/lib/modules/manager/npm/post-update/index.spec.ts +++ b/lib/modules/manager/npm/post-update/index.spec.ts @@ -598,23 +598,6 @@ describe('modules/manager/npm/post-update/index', () => { }); }); - it('skip transitive remediation', async () => { - expect( - await getAdditionalFiles( - { - ...baseConfig, - upgrades: [{ isVulnerabilityAlert: true }], - transitiveRemediation: true, - updateLockFiles: true, - }, - additionalFiles, - ), - ).toStrictEqual({ - artifactErrors: [], - updatedArtifacts: [], - }); - }); - it('fails for npm', async () => { spyNpm.mockResolvedValueOnce({ error: true, stderr: 'some-error' }); expect( diff --git a/lib/modules/manager/npm/post-update/index.ts b/lib/modules/manager/npm/post-update/index.ts index 76265c2218f7eb..ea1e65d8e5a811 100644 --- a/lib/modules/manager/npm/post-update/index.ts +++ b/lib/modules/manager/npm/post-update/index.ts @@ -419,16 +419,6 @@ export async function getAdditionalFiles( logger.debug('Skipping lock file generation'); return { artifactErrors, updatedArtifacts }; } - if ( - !config.updatedPackageFiles?.length && - config.transitiveRemediation && - config.upgrades?.every( - (upgrade) => upgrade.isRemediation ?? upgrade.isVulnerabilityAlert, - ) - ) { - logger.debug('Skipping lock file generation for remediations'); - return { artifactErrors, updatedArtifacts }; - } if ( config.reuseExistingBranch && !config.updatedPackageFiles?.length && diff --git a/lib/modules/manager/npm/post-update/yarn.spec.ts b/lib/modules/manager/npm/post-update/yarn.spec.ts index d1e01dc27a1e03..edce9e9dad8012 100644 --- a/lib/modules/manager/npm/post-update/yarn.spec.ts +++ b/lib/modules/manager/npm/post-update/yarn.spec.ts @@ -48,7 +48,6 @@ describe('modules/manager/npm/post-update/yarn', () => { delete process.env.BUILDPACK; delete process.env.HTTP_PROXY; delete process.env.HTTPS_PROXY; - delete process.env.RENOVATE_X_YARN_PROXY; Fixtures.reset(); GlobalConfig.set({ localDir: '.', cacheDir: '/tmp/cache' }); removeDockerContainer.mockResolvedValue(); @@ -153,11 +152,11 @@ describe('modules/manager/npm/post-update/yarn', () => { it('sets http proxy', async () => { process.env.HTTP_PROXY = 'http://proxy'; process.env.HTTPS_PROXY = 'http://proxy'; - process.env.RENOVATE_X_YARN_PROXY = 'true'; GlobalConfig.set({ localDir: '.', allowScripts: true, cacheDir: '/tmp/cache', + experimentalFlags: ['yarnProxy'], }); Fixtures.mock( { diff --git a/lib/modules/manager/npm/post-update/yarn.ts b/lib/modules/manager/npm/post-update/yarn.ts index 3334a8d6167419..967f76f75f2c5b 100644 --- a/lib/modules/manager/npm/post-update/yarn.ts +++ b/lib/modules/manager/npm/post-update/yarn.ts @@ -210,7 +210,7 @@ export async function generateLockFile( commands.push(`yarn set version ${quote(yarnUpdate.newValue!)}`); } - if (process.env.RENOVATE_X_YARN_PROXY) { + if (GlobalConfig.getExperimentalFlag('yarnProxy')) { if (process.env.HTTP_PROXY && !isYarn1) { commands.push('yarn config unset --home httpProxy'); commands.push( diff --git a/lib/modules/manager/npm/readme.md b/lib/modules/manager/npm/readme.md index a5d8549e57ab5f..750e46f307aa32 100644 --- a/lib/modules/manager/npm/readme.md +++ b/lib/modules/manager/npm/readme.md @@ -18,7 +18,7 @@ If Renovate detects a `packageManager` setting for Yarn in `package.json` then i Yarn itself does not natively recognize/support the `HTTP_PROXY` and `HTTPS_PROXY` environment variables. -You can configure `RENOVATE_X_YARN_PROXY=true` as an environment variable to enable configuring of Yarn proxy (e.g. if you cannot configure these proxy settings yourself in `~/.yarnrc.yml`). +You can add `yarnProxy` in the `experimentalFlags` array in your configuration to enable configuring of Yarn proxy (e.g. if you cannot configure these proxy settings yourself in `~/.yarnrc.yml`). If set, and Renovate detects Yarn 2+, and one or both of those variables are present, then Renovate will run commands like `yarn config set --home httpProxy http://proxy` prior to executing `yarn install`. This will result in the `~/.yarnrc.yml` file being created or modified with these settings, and the settings are not removed afterwards. diff --git a/lib/modules/manager/pep621/extract.spec.ts b/lib/modules/manager/pep621/extract.spec.ts index e7147e912b4b72..d86e5d419ad7c1 100644 --- a/lib/modules/manager/pep621/extract.spec.ts +++ b/lib/modules/manager/pep621/extract.spec.ts @@ -134,14 +134,14 @@ describe('modules/manager/pep621/extract', () => { datasource: 'pypi', depType: 'project.optional-dependencies', currentValue: '>12', - depName: 'pytest/pytest', + depName: 'pytest', }, { packageName: 'pytest-mock', datasource: 'pypi', depType: 'project.optional-dependencies', skipReason: 'unspecified-version', - depName: 'pytest/pytest-mock', + depName: 'pytest-mock', }, ]); @@ -154,28 +154,28 @@ describe('modules/manager/pep621/extract', () => { datasource: 'pypi', depType: 'tool.pdm.dev-dependencies', skipReason: 'unspecified-version', - depName: 'test/pdm', + depName: 'pdm', }, { packageName: 'pytest-rerunfailures', datasource: 'pypi', depType: 'tool.pdm.dev-dependencies', currentValue: '>=10.2', - depName: 'test/pytest-rerunfailures', + depName: 'pytest-rerunfailures', }, { packageName: 'tox', datasource: 'pypi', depType: 'tool.pdm.dev-dependencies', skipReason: 'unspecified-version', - depName: 'tox/tox', + depName: 'tox', }, { packageName: 'tox-pdm', datasource: 'pypi', depType: 'tool.pdm.dev-dependencies', currentValue: '>=0.5', - depName: 'tox/tox-pdm', + depName: 'tox-pdm', }, ]); }); @@ -214,7 +214,7 @@ describe('modules/manager/pep621/extract', () => { datasource: 'pypi', depType: 'project.optional-dependencies', currentValue: '>12', - depName: 'pytest/pytest', + depName: 'pytest', registryUrls: [ 'https://private-site.org/pypi/simple', 'https://private.pypi.org/simple', @@ -225,7 +225,7 @@ describe('modules/manager/pep621/extract', () => { datasource: 'pypi', depType: 'tool.pdm.dev-dependencies', currentValue: '>=10.2', - depName: 'test/pytest-rerunfailures', + depName: 'pytest-rerunfailures', registryUrls: [ 'https://private-site.org/pypi/simple', 'https://private.pypi.org/simple', @@ -236,7 +236,7 @@ describe('modules/manager/pep621/extract', () => { datasource: 'pypi', depType: 'tool.pdm.dev-dependencies', currentValue: '>=0.5', - depName: 'tox/tox-pdm', + depName: 'tox-pdm', registryUrls: [ 'https://private-site.org/pypi/simple', 'https://private.pypi.org/simple', diff --git a/lib/modules/manager/pep621/utils.ts b/lib/modules/manager/pep621/utils.ts index d81deb8b987614..dcc3bb8cc2f447 100644 --- a/lib/modules/manager/pep621/utils.ts +++ b/lib/modules/manager/pep621/utils.ts @@ -3,6 +3,7 @@ import { logger } from '../../../logger'; import { regEx } from '../../../util/regex'; import { parse as parseToml } from '../../../util/toml'; import { PypiDatasource } from '../../datasource/pypi'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; import type { PackageDependency } from '../types'; import { PyProject, PyProjectSchema } from './schema'; import type { Pep508ParseResult } from './types'; @@ -60,7 +61,7 @@ export function pep508ToPackageDependency( } const dep: PackageDependency = { - packageName: parsed.packageName, + packageName: normalizePythonDepName(parsed.packageName), depName: parsed.packageName, datasource: PypiDatasource.id, depType, @@ -87,9 +88,9 @@ export function parseDependencyGroupRecord( } const deps: PackageDependency[] = []; - for (const [groupName, pep508Strings] of Object.entries(records)) { + for (const pep508Strings of Object.values(records)) { for (const dep of parseDependencyList(depType, pep508Strings)) { - deps.push({ ...dep, depName: `${groupName}/${dep.packageName!}` }); + deps.push({ ...dep, depName: dep.packageName! }); } } return deps; diff --git a/lib/modules/manager/pip-compile/extract.spec.ts b/lib/modules/manager/pip-compile/extract.spec.ts index cab96c8f09c36e..fd70448a9a3e1e 100644 --- a/lib/modules/manager/pip-compile/extract.spec.ts +++ b/lib/modules/manager/pip-compile/extract.spec.ts @@ -384,6 +384,7 @@ describe('modules/manager/pip-compile/extract', () => { datasource: 'pypi', depType: 'indirect', depName: 'bards-friend', + packageName: 'bards-friend', lockedVersion: '1.0.0', enabled: false, }); diff --git a/lib/modules/manager/pip-compile/extract.ts b/lib/modules/manager/pip-compile/extract.ts index 9960fb3b4c7b9c..2be0ad3fd37135 100644 --- a/lib/modules/manager/pip-compile/extract.ts +++ b/lib/modules/manager/pip-compile/extract.ts @@ -2,7 +2,6 @@ import upath from 'upath'; import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; import { ensureLocalPath } from '../../../util/fs/util'; -import { normalizePythonDepName } from '../../datasource/pypi/common'; import { extractPackageFile as extractRequirementsFile } from '../pip_requirements/extract'; import { extractPackageFile as extractSetupPyFile } from '../pip_setup'; import type { @@ -165,9 +164,7 @@ export async function extractAllPackageFiles( } for (const dep of packageFileContent.deps) { const lockedVersion = lockedDeps?.find( - (lockedDep) => - normalizePythonDepName(lockedDep.depName!) === - normalizePythonDepName(dep.depName!), + (lockedDep) => lockedDep.packageName! === dep.packageName!, )?.currentVersion; if (lockedVersion) { dep.lockedVersion = lockedVersion; @@ -246,9 +243,7 @@ function extendWithIndirectDeps( for (const lockedDep of lockedDeps) { if ( !packageFileContent.deps.find( - (dep) => - normalizePythonDepName(lockedDep.depName!) === - normalizePythonDepName(dep.depName!), + (dep) => lockedDep.packageName! === dep.packageName!, ) ) { packageFileContent.deps.push(indirectDep(lockedDep)); diff --git a/lib/modules/manager/pip_requirements/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/pip_requirements/__snapshots__/extract.spec.ts.snap index beb0d0c9f52661..4bbeef11ea7728 100644 --- a/lib/modules/manager/pip_requirements/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/pip_requirements/__snapshots__/extract.spec.ts.snap @@ -8,23 +8,27 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() extracts "currentVersion": "0.3.1", "datasource": "pypi", "depName": "some-package", + "packageName": "some-package", }, { "currentValue": "==1.0.0", "currentVersion": "1.0.0", "datasource": "pypi", "depName": "some-other-package", + "packageName": "some-other-package", }, { "currentValue": undefined, "datasource": "pypi", "depName": "sphinx", + "packageName": "sphinx", }, { "currentValue": "==1.9", "currentVersion": "1.9", "datasource": "pypi", "depName": "not_semver", + "packageName": "not-semver", }, ], "registryUrls": [ @@ -40,30 +44,35 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() extracts "currentVersion": "1", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==0.6.27", "currentVersion": "0.6.27", "datasource": "pypi", "depName": "distribute", + "packageName": "distribute", }, { "currentValue": "==0.2", "currentVersion": "0.2", "datasource": "pypi", "depName": "dj-database-url", + "packageName": "dj-database-url", }, { "currentValue": "==2.4.5", "currentVersion": "2.4.5", "datasource": "pypi", "depName": "psycopg2", + "packageName": "psycopg2", }, { "currentValue": "==0.1.2", "currentVersion": "0.1.2", "datasource": "pypi", "depName": "wsgiref", + "packageName": "wsgiref", }, ] `; @@ -75,12 +84,14 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() handles c "currentVersion": "1.11.23", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==0.6.27", "currentVersion": "0.6.27", "datasource": "pypi", "depName": "distribute", + "packageName": "distribute", "skipReason": "ignored", }, { @@ -88,18 +99,21 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() handles c "currentVersion": "0.2", "datasource": "pypi", "depName": "dj-database-url", + "packageName": "dj-database-url", }, { "currentValue": "==2.4.5", "currentVersion": "2.4.5", "datasource": "pypi", "depName": "psycopg2", + "packageName": "psycopg2", }, { "currentValue": "==0.1.2", "currentVersion": "0.1.2", "datasource": "pypi", "depName": "wsgiref", + "packageName": "wsgiref", }, ] `; @@ -115,36 +129,42 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() handles e "currentVersion": "2.0.12", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==4.1.1", "currentVersion": "4.1.1", "datasource": "pypi", "depName": "celery", + "packageName": "celery", }, { "currentValue": "== 3.2.1", "currentVersion": "3.2.1", "datasource": "pypi", "depName": "foo", + "packageName": "foo", }, { "currentValue": "==0.3.1", "currentVersion": "0.3.1", "datasource": "pypi", "depName": "some-package", + "packageName": "some-package", }, { "currentValue": "==1.0.0", "currentVersion": "1.0.0", "datasource": "pypi", "depName": "some-other-package", + "packageName": "some-other-package", }, { "currentValue": "==1.9", "currentVersion": "1.9", "datasource": "pypi", "depName": "not_semver", + "packageName": "not-semver", }, ], "registryUrls": [ @@ -164,36 +184,42 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() handles e "currentVersion": "2.0.12", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==4.1.1", "currentVersion": "4.1.1", "datasource": "pypi", "depName": "celery", + "packageName": "celery", }, { "currentValue": "== 3.2.1", "currentVersion": "3.2.1", "datasource": "pypi", "depName": "foo", + "packageName": "foo", }, { "currentValue": "==0.3.1", "currentVersion": "0.3.1", "datasource": "pypi", "depName": "some-package", + "packageName": "some-package", }, { "currentValue": "==1.0.0", "currentVersion": "1.0.0", "datasource": "pypi", "depName": "some-other-package", + "packageName": "some-other-package", }, { "currentValue": "==1.9", "currentVersion": "1.9", "datasource": "pypi", "depName": "not_semver", + "packageName": "not-semver", }, ], } @@ -210,36 +236,42 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() handles e "currentVersion": "2.0.12", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==4.1.1", "currentVersion": "4.1.1", "datasource": "pypi", "depName": "celery", + "packageName": "celery", }, { "currentValue": "== 3.2.1", "currentVersion": "3.2.1", "datasource": "pypi", "depName": "foo", + "packageName": "foo", }, { "currentValue": "==0.3.1", "currentVersion": "0.3.1", "datasource": "pypi", "depName": "some-package", + "packageName": "some-package", }, { "currentValue": "==1.0.0", "currentVersion": "1.0.0", "datasource": "pypi", "depName": "some-other-package", + "packageName": "some-other-package", }, { "currentValue": "==1.9", "currentVersion": "1.9", "datasource": "pypi", "depName": "not_semver", + "packageName": "not-semver", }, ], } @@ -253,18 +285,21 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() handles e "currentVersion": "2.0.12", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==4.1.1", "currentVersion": "4.1.1", "datasource": "pypi", "depName": "celery", + "packageName": "celery", }, { "currentValue": "== 3.2.1", "currentVersion": "3.2.1", "datasource": "pypi", "depName": "foo", + "packageName": "foo", }, ], "registryUrls": [ @@ -281,18 +316,21 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() handles e "currentVersion": "2.0.12", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==4.1.1", "currentVersion": "4.1.1", "datasource": "pypi", "depName": "celery", + "packageName": "celery", }, { "currentValue": "== 3.2.1", "currentVersion": "3.2.1", "datasource": "pypi", "depName": "foo", + "packageName": "foo", }, ], "registryUrls": [ @@ -309,18 +347,21 @@ exports[`modules/manager/pip_requirements/extract extractPackageFile() should ha "currentVersion": "1.9.1", "datasource": "pypi", "depName": "Django", + "packageName": "django", }, { "currentValue": "==0.22.1", "currentVersion": "0.22.1", "datasource": "pypi", "depName": "bgg", + "packageName": "bgg", }, { "currentValue": "==2016.1.8", "currentVersion": "2016.1.8", "datasource": "pypi", "depName": "html2text", + "packageName": "html2text", }, ], } diff --git a/lib/modules/manager/pip_requirements/extract.spec.ts b/lib/modules/manager/pip_requirements/extract.spec.ts index 76932dce197188..f088de89f67c4c 100644 --- a/lib/modules/manager/pip_requirements/extract.spec.ts +++ b/lib/modules/manager/pip_requirements/extract.spec.ts @@ -190,6 +190,7 @@ some-package==0.3.1`; currentVersion: '20.3.0', datasource: 'pypi', depName: 'attrs', + packageName: 'attrs', }, ], }); diff --git a/lib/modules/manager/pip_requirements/extract.ts b/lib/modules/manager/pip_requirements/extract.ts index 3d1a3a7bfcc72e..1921ae49a9fba1 100644 --- a/lib/modules/manager/pip_requirements/extract.ts +++ b/lib/modules/manager/pip_requirements/extract.ts @@ -6,6 +6,7 @@ import { isSkipComment } from '../../../util/ignore'; import { newlineRegex, regEx } from '../../../util/regex'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { PypiDatasource } from '../../datasource/pypi'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; import type { PackageDependency, PackageFileContent } from '../types'; import { extractPackageFileFlags } from './common'; import type { PipRequirementsManagerData } from './types'; @@ -81,6 +82,7 @@ export function extractPackageFile( dep = { ...dep, depName, + packageName: normalizePythonDepName(depName), currentValue, datasource: PypiDatasource.id, }; diff --git a/lib/modules/manager/pip_setup/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/pip_setup/__snapshots__/extract.spec.ts.snap index 7246f4f88acf8b..9ebc8247dc7a11 100644 --- a/lib/modules/manager/pip_setup/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/pip_setup/__snapshots__/extract.spec.ts.snap @@ -10,6 +10,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 49, }, + "packageName": "celery", }, { "currentValue": ">=1.7", @@ -18,6 +19,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 52, }, + "packageName": "logging-tree", }, { "currentValue": ">=2.2", @@ -26,6 +28,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 53, }, + "packageName": "pygments", }, { "currentValue": ">=5.0", @@ -34,6 +37,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 54, }, + "packageName": "psutil", }, { "currentValue": ">=3.0", @@ -42,6 +46,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 55, }, + "packageName": "objgraph", }, { "currentValue": ">=1.11.23,<2.0", @@ -50,6 +55,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 58, }, + "packageName": "django", }, { "currentValue": ">=0.11,<2.0", @@ -58,6 +64,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 61, }, + "packageName": "flask", }, { "currentValue": ">=1.4,<2.0", @@ -66,6 +73,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 62, }, + "packageName": "blinker", }, { "currentValue": ">=19.7.0,<20.0", @@ -74,6 +82,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 74, }, + "packageName": "gunicorn", }, { "currentValue": ">=0.15.3,<0.16", @@ -82,6 +91,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 75, }, + "packageName": "werkzeug", }, { "currentValue": ">=3.2.1,<4.0", @@ -90,6 +100,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 75, }, + "packageName": "statsd", }, { "currentValue": ">=2.10.0,<3.0", @@ -98,6 +109,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 76, }, + "packageName": "requests", "skipReason": "ignored", }, { @@ -107,6 +119,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 77, }, + "packageName": "raven", }, { "currentValue": ">=0.15.2,<0.17", @@ -115,6 +128,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 78, }, + "packageName": "future", }, { "currentValue": ">=1.0.16,<2.0", @@ -123,6 +137,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 79, }, + "packageName": "ipaddress", }, { "currentValue": ">=5.5.2,<6.0.0", @@ -131,6 +146,7 @@ exports[`modules/manager/pip_setup/extract extractPackageFile() returns found de "managerData": { "lineNumber": 80, }, + "packageName": "zope-interface", }, ], } diff --git a/lib/modules/manager/pip_setup/extract.ts b/lib/modules/manager/pip_setup/extract.ts index cef10e2322de8c..d1bb84626b572d 100644 --- a/lib/modules/manager/pip_setup/extract.ts +++ b/lib/modules/manager/pip_setup/extract.ts @@ -2,6 +2,7 @@ import { RANGE_PATTERN } from '@renovatebot/pep440'; import { lang, lexer, parser, query as q } from 'good-enough-parser'; import { regEx } from '../../../util/regex'; import { PypiDatasource } from '../../datasource/pypi'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; import type { ExtractConfig, PackageDependency, @@ -45,6 +46,7 @@ function depStringHandler( const dep: PackageDependency = { depName, + packageName: normalizePythonDepName(depName), currentValue, managerData: { lineNumber: token.line - 1, diff --git a/lib/modules/manager/pipenv/extract.ts b/lib/modules/manager/pipenv/extract.ts index 10cc367e370f41..cfdae749cb879e 100644 --- a/lib/modules/manager/pipenv/extract.ts +++ b/lib/modules/manager/pipenv/extract.ts @@ -6,6 +6,7 @@ import { localPathExists } from '../../../util/fs'; import { regEx } from '../../../util/regex'; import { parse as parseToml } from '../../../util/toml'; import { PypiDatasource } from '../../datasource/pypi'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; import type { PackageDependency, PackageFileContent } from '../types'; import type { PipFile, PipRequirement, PipSource } from './types'; @@ -76,6 +77,7 @@ function extractFromSection( const dep: PackageDependency = { depType: sectionName, depName, + packageName: normalizePythonDepName(depName), managerData: {}, }; if (currentValue) { diff --git a/lib/modules/manager/setup-cfg/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/setup-cfg/__snapshots__/extract.spec.ts.snap index bef317998567c0..f0be467fd0e341 100644 --- a/lib/modules/manager/setup-cfg/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/setup-cfg/__snapshots__/extract.spec.ts.snap @@ -8,12 +8,14 @@ exports[`modules/manager/setup-cfg/extract extractPackageFile() extracts depende "datasource": "pypi", "depName": "coloredlogs", "depType": "install", + "packageName": "coloredlogs", }, { "currentValue": "~=1.0", "datasource": "pypi", "depName": "first", "depType": "install", + "packageName": "first", }, { "currentValue": "==2.2", @@ -21,48 +23,56 @@ exports[`modules/manager/setup-cfg/extract extractPackageFile() extracts depende "datasource": "pypi", "depName": "second", "depType": "install", + "packageName": "second", }, { "currentValue": ">=3.0", "datasource": "pypi", "depName": "third", "depType": "install", + "packageName": "third", }, { "currentValue": ">=5.5.5", "datasource": "pypi", "depName": "quux", "depType": "install", + "packageName": "quux", }, { "currentValue": "~=2.1", "datasource": "pypi", "depName": "python-dateutil", "depType": "install", + "packageName": "python-dateutil", }, { "currentValue": ">=1.1.1", "datasource": "pypi", "depName": "foo", "depType": "install", + "packageName": "foo", }, { "currentValue": ">=3.3.3.", "datasource": "pypi", "depName": "baz", "depType": "install", + "packageName": "baz", }, { "currentValue": "~=0.4", "datasource": "pypi", "depName": "docopt", "depType": "install", + "packageName": "docopt", }, { "currentValue": "~=2.1", "datasource": "pypi", "depName": "fs", "depType": "install", + "packageName": "fs", }, { "currentValue": "==1.0", @@ -70,198 +80,231 @@ exports[`modules/manager/setup-cfg/extract extractPackageFile() extracts depende "datasource": "pypi", "depName": "nmspc.pkg", "depType": "install", + "packageName": "nmspc-pkg", }, { "currentValue": "~=2.18", "datasource": "pypi", "depName": "requests", "depType": "install", + "packageName": "requests", }, { "currentValue": "~=1.2.3", "datasource": "pypi", "depName": "compact", "depType": "install", + "packageName": "compact", }, { "currentValue": ">=2.27.0", "datasource": "pypi", "depName": "responses", "depType": "install", + "packageName": "responses", }, { "currentValue": "~=1.4", "datasource": "pypi", "depName": "six", "depType": "setup", + "packageName": "six", }, { "currentValue": "~=4.19", "datasource": "pypi", "depName": "tqdm", "depType": "setup", + "packageName": "tqdm", }, { "currentValue": "~=6.0", "datasource": "pypi", "depName": "tenacity", "depType": "setup", + "packageName": "tenacity", }, { "currentValue": "~=3.6", "datasource": "pypi", "depName": "typing", "depType": "test", + "packageName": "typing", }, { "currentValue": "~=1.7", "datasource": "pypi", "depName": "verboselogs", "depType": "test", + "packageName": "verboselogs", }, { "currentValue": undefined, "datasource": "pypi", "depName": "piexif", "depType": "extra", + "packageName": "piexif", }, { "currentValue": undefined, "datasource": "pypi", "depName": "Pillow", "depType": "extra", + "packageName": "pillow", }, { "currentValue": ">=2.2.2", "datasource": "pypi", "depName": "bar", "depType": "extra", + "packageName": "bar", }, { "currentValue": ">=4.4.4", "datasource": "pypi", "depName": "qux", "depType": "extra", + "packageName": "qux", }, { "currentValue": "~=0.1", "datasource": "pypi", "depName": "contexter", "depType": "extra", + "packageName": "contexter", }, { "currentValue": "~=2.0", "datasource": "pypi", "depName": "mock", "depType": "extra", + "packageName": "mock", }, { "currentValue": "~=0.6", "datasource": "pypi", "depName": "parameterized", "depType": "extra", + "packageName": "parameterized", }, { "currentValue": "~=2.12", "datasource": "pypi", "depName": "green", "depType": "extra", + "packageName": "green", }, { "currentValue": undefined, "datasource": "pypi", "depName": "coverage", "depType": "extra", + "packageName": "coverage", }, { "currentValue": undefined, "datasource": "pypi", "depName": "codecov", "depType": "extra", + "packageName": "codecov", }, { "currentValue": undefined, "datasource": "pypi", "depName": "codacy-coverage", "depType": "extra", + "packageName": "codacy-coverage", }, { "currentValue": "~=1.7", "datasource": "pypi", "depName": "sphinx", "depType": "extra", + "packageName": "sphinx", }, { "currentValue": "~=0.6", "datasource": "pypi", "depName": "sphinx-bootstrap-theme", "depType": "extra", + "packageName": "sphinx-bootstrap-theme", }, { "currentValue": "~=2.6", "datasource": "pypi", "depName": "semantic-version", "depType": "extra", + "packageName": "semantic-version", }, { "currentValue": undefined, "datasource": "pypi", "depName": "docutils", "depType": "extra", + "packageName": "docutils", }, { "currentValue": undefined, "datasource": "pypi", "depName": "Pygments", "depType": "extra", + "packageName": "pygments", }, { "currentValue": ">=0.9", "datasource": "pypi", "depName": "aiortc", "depType": "install", + "packageName": "aiortc", }, { "currentValue": ">=8.1", "datasource": "pypi", "depName": "websockets", "depType": "install", + "packageName": "websockets", }, { "currentValue": ">=3.6", "datasource": "pypi", "depName": "aiohttp", "depType": "install", + "packageName": "aiohttp", }, { "currentValue": ">=6.0", "datasource": "pypi", "depName": "pyee", "depType": "install", + "packageName": "pyee", }, { "currentValue": ">=8.1", "datasource": "pypi", "depName": "websockets", "depType": "install", + "packageName": "websockets", }, { "currentValue": ">=0.3", "datasource": "pypi", "depName": "dataclasses_json", "depType": "install", + "packageName": "dataclasses-json", }, { "currentValue": ">=10.0", "datasource": "pypi", "depName": "coloredlogs", "depType": "install", + "packageName": "coloredlogs", }, { "currentValue": "~=8.0.0", "datasource": "pypi", "depName": "av", "depType": "install", + "packageName": "av", }, ], } diff --git a/lib/modules/manager/setup-cfg/extract.ts b/lib/modules/manager/setup-cfg/extract.ts index 3aaca6352305a1..017f9903ad30da 100644 --- a/lib/modules/manager/setup-cfg/extract.ts +++ b/lib/modules/manager/setup-cfg/extract.ts @@ -3,6 +3,7 @@ import { RANGE_PATTERN } from '@renovatebot/pep440'; import { logger } from '../../../logger'; import { newlineRegex, regEx } from '../../../util/regex'; import { PypiDatasource } from '../../datasource/pypi'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; import type { PackageDependency, PackageFileContent, Result } from '../types'; function getSectionName(str: string): string { @@ -73,6 +74,7 @@ function parseDep( const dep: PackageDependency = { depName, + packageName: normalizePythonDepName(depName), currentValue, datasource: PypiDatasource.id, depType, diff --git a/lib/modules/platform/default-scm.spec.ts b/lib/modules/platform/default-scm.spec.ts index 198884aa5aa3fa..4b4c6360aaff9c 100644 --- a/lib/modules/platform/default-scm.spec.ts +++ b/lib/modules/platform/default-scm.spec.ts @@ -45,7 +45,7 @@ describe('modules/platform/default-scm', () => { it('delegate isBranchModified to util/git', async () => { git.isBranchModified.mockResolvedValueOnce(true); - await defaultGitScm.isBranchModified('branchName'); + await defaultGitScm.isBranchModified('branchName', 'main'); expect(git.isBranchModified).toHaveBeenCalledTimes(1); }); diff --git a/lib/modules/platform/default-scm.ts b/lib/modules/platform/default-scm.ts index c3b799405ffd01..edf27c2e391566 100644 --- a/lib/modules/platform/default-scm.ts +++ b/lib/modules/platform/default-scm.ts @@ -29,8 +29,8 @@ export class DefaultGitScm implements PlatformScm { return git.isBranchConflicted(baseBranch, branch); } - isBranchModified(branchName: string): Promise { - return git.isBranchModified(branchName); + isBranchModified(branchName: string, baseBranch: string): Promise { + return git.isBranchModified(branchName, baseBranch); } getFileList(): Promise { diff --git a/lib/modules/platform/github/graphql.ts b/lib/modules/platform/github/graphql.ts index 3931dd47d56257..337ea831208ccc 100644 --- a/lib/modules/platform/github/graphql.ts +++ b/lib/modules/platform/github/graphql.ts @@ -68,34 +68,6 @@ query( } `; -export const vulnerabilityAlertsQuery = (filterByState: boolean): string => ` -query($owner: String!, $name: String!) { - repository(owner: $owner, name: $name) { - vulnerabilityAlerts(last: 100, ${filterByState ? 'states: [OPEN]' : ''}) { - edges { - node { - dismissReason - vulnerableManifestFilename - vulnerableManifestPath - vulnerableRequirements - securityAdvisory { - description - identifiers { type value } - references { url } - severity - } - securityVulnerability { - package { name ecosystem } - firstPatchedVersion { identifier } - vulnerableVersionRange - } - } - } - } - } -} -`; - export const enableAutoMergeMutation = ` mutation EnablePullRequestAutoMerge( $pullRequestId: ID!, diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 4c1d2a51d0bbbb..d54b836b0c7f54 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -3770,147 +3770,118 @@ describe('modules/platform/github/index', () => { }); it('returns empty if error', async () => { - httpMock.scope(githubApiHost).post('/graphql').reply(200, {}); + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get( + '/repos/some/repo/dependabot/alerts?state=open&direction=asc&per_page=100', + ) + .reply(200, {}); + await github.initRepo({ repository: 'some/repo' }); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); }); it('returns array if found', async () => { - httpMock - .scope(githubApiHost) - .post('/graphql') - .reply(200, { - data: { - repository: { - vulnerabilityAlerts: { - edges: [ - { - node: { - securityAdvisory: { severity: 'HIGH', references: [] }, - securityVulnerability: { - package: { - ecosystem: 'NPM', - name: 'left-pad', - range: '0.0.2', - }, - vulnerableVersionRange: '0.0.2', - firstPatchedVersion: { identifier: '0.0.3' }, - }, - vulnerableManifestFilename: 'foo', - vulnerableManifestPath: 'bar', - }, - }, - ], - }, + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get( + '/repos/some/repo/dependabot/alerts?state=open&direction=asc&per_page=100', + ) + .reply(200, [ + { + security_advisory: { + description: 'description', + identifiers: [{ type: 'type', value: 'value' }], + references: [], }, - }, - }); - const res = await github.getVulnerabilityAlerts(); - expect(res).toHaveLength(1); - }); - - it('returns array if found on GHE', async () => { - const gheApiHost = 'https://ghe.renovatebot.com'; - - httpMock - .scope(gheApiHost) - .head('/') - .reply(200, '', { 'x-github-enterprise-version': '3.0.15' }) - .get('/user') - .reply(200, { login: 'renovate-bot' }) - .get('/user/emails') - .reply(200, {}); - - httpMock - .scope(gheApiHost) - .post('/graphql') - .reply(200, { - data: { - repository: { - vulnerabilityAlerts: { - edges: [ - { - node: { - securityAdvisory: { severity: 'HIGH', references: [] }, - securityVulnerability: { - package: { - ecosystem: 'NPM', - name: 'left-pad', - range: '0.0.2', - }, - vulnerableVersionRange: '0.0.2', - firstPatchedVersion: { identifier: '0.0.3' }, - }, - vulnerableManifestFilename: 'foo', - vulnerableManifestPath: 'bar', - }, - }, - ], + security_vulnerability: { + package: { + ecosystem: 'npm', + name: 'left-pad', }, + vulnerable_version_range: '0.0.2', + first_patched_version: { identifier: '0.0.3' }, + }, + dependency: { + manifest_path: 'bar/foo', }, }, - }); - - await github.initPlatform({ - endpoint: gheApiHost, - token: '123test', - }); - + ]); + await github.initRepo({ repository: 'some/repo' }); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(1); }); it('returns empty if disabled', async () => { // prettier-ignore - httpMock.scope(githubApiHost).post('/graphql').reply(200, {data: {repository: {}}}); + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get( + '/repos/some/repo/dependabot/alerts?state=open&direction=asc&per_page=100', + ) + .reply(200, { data: { repository: {} } }); + await github.initRepo({ repository: 'some/repo' }); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); }); it('handles network error', async () => { // prettier-ignore - httpMock.scope(githubApiHost).post('/graphql').replyWithError('unknown error'); + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get( + '/repos/some/repo/dependabot/alerts?state=open&direction=asc&per_page=100', + ) + .replyWithError('unknown error'); + await github.initRepo({ repository: 'some/repo' }); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); }); it('calls logger.debug with only items that include securityVulnerability', async () => { - httpMock - .scope(githubApiHost) - .post('/graphql') - .reply(200, { - data: { - repository: { - vulnerabilityAlerts: { - edges: [ - { - node: { - securityAdvisory: { severity: 'HIGH', references: [] }, - securityVulnerability: { - package: { - ecosystem: 'NPM', - name: 'left-pad', - }, - vulnerableVersionRange: '0.0.2', - firstPatchedVersion: { identifier: '0.0.3' }, - }, - vulnerableManifestFilename: 'foo', - vulnerableManifestPath: 'bar', - }, - }, - { - node: { - securityAdvisory: { severity: 'HIGH', references: [] }, - securityVulnerability: null, - vulnerableManifestFilename: 'foo', - vulnerableManifestPath: 'bar', - }, - }, - ], + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get( + '/repos/some/repo/dependabot/alerts?state=open&direction=asc&per_page=100', + ) + .reply(200, [ + { + security_advisory: { + description: 'description', + identifiers: [{ type: 'type', value: 'value' }], + references: [], + }, + security_vulnerability: { + package: { + ecosystem: 'npm', + name: 'left-pad', }, + vulnerable_version_range: '0.0.2', + first_patched_version: { identifier: '0.0.3' }, + }, + dependency: { + manifest_path: 'bar/foo', }, }, - }); + + { + security_advisory: { + description: 'description', + identifiers: [{ type: 'type', value: 'value' }], + references: [], + }, + security_vulnerability: null, + dependency: { + manifest_path: 'bar/foo', + }, + }, + ]); + await github.initRepo({ repository: 'some/repo' }); await github.getVulnerabilityAlerts(); expect(logger.logger.debug).toHaveBeenCalledWith( { alerts: { 'npm/left-pad': { '0.0.2': '0.0.3' } } }, @@ -3918,6 +3889,38 @@ describe('modules/platform/github/index', () => { ); expect(logger.logger.error).not.toHaveBeenCalled(); }); + + it('returns normalized names for PIP ecosystem', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get( + '/repos/some/repo/dependabot/alerts?state=open&direction=asc&per_page=100', + ) + .reply(200, [ + { + security_advisory: { + description: 'description', + identifiers: [{ type: 'type', value: 'value' }], + references: [], + }, + security_vulnerability: { + package: { + ecosystem: 'pip', + name: 'FrIeNdLy.-.BARD', + }, + vulnerable_version_range: '0.0.2', + first_patched_version: { identifier: '0.0.3' }, + }, + dependency: { + manifest_path: 'bar/foo', + }, + }, + ]); + await github.initRepo({ repository: 'some/repo' }); + const res = await github.getVulnerabilityAlerts(); + expect(res[0].security_vulnerability!.package.name).toBe('friendly-bard'); + }); }); describe('getJsonFile()', () => { diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index a0873b7d0c09b6..4c15816f6ff522 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -64,6 +64,7 @@ import type { UpdatePrConfig, } from '../types'; import { repoFingerprint } from '../util'; +import { normalizeNamePerEcosystem } from '../utils/github-alerts'; import { smartTruncate } from '../utils/pr-body'; import { remoteBranchExists } from './branch'; import { coerceRestPr, githubApi } from './common'; @@ -71,11 +72,11 @@ import { enableAutoMergeMutation, getIssuesQuery, repoInfoQuery, - vulnerabilityAlertsQuery, } from './graphql'; import { GithubIssueCache, GithubIssue as Issue } from './issue'; import { massageMarkdownLinks } from './massage-markdown-links'; import { getPrCache, updatePrCache } from './pr'; +import { VulnerabilityAlertSchema } from './schema'; import type { BranchProtection, CombinedBranchStatus, @@ -112,6 +113,10 @@ function escapeHash(input: string): string { return input?.replace(regEx(/#/g), '%23'); } +export function isGHApp(): boolean { + return !!platformConfig.isGHApp; +} + export async function detectGhe(token: string): Promise { platformConfig.isGhe = URL.parse(platformConfig.endpoint).host !== 'api.github.com'; @@ -245,7 +250,7 @@ export async function initPlatform({ async function fetchRepositories(): Promise { try { - if (platformConfig.isGHApp) { + if (isGHApp()) { const res = await githubApi.getJson<{ repositories: GhRestRepo[]; }>(`installation/repositories?per_page=100`, { @@ -1960,27 +1965,19 @@ export async function getVulnerabilityAlerts(): Promise { logger.debug('No vulnerability alerts enabled for repo'); return []; } - let vulnerabilityAlerts: { node: VulnerabilityAlert }[] | undefined; - - // TODO #22198 - const gheSupportsStateFilter = semver.satisfies( - // semver not null safe, accepts null and undefined - - platformConfig.gheVersion!, - '>=3.5', - ); - const filterByState = !platformConfig.isGhe || gheSupportsStateFilter; - const query = vulnerabilityAlertsQuery(filterByState); - + let vulnerabilityAlerts: VulnerabilityAlert[] | undefined; try { - vulnerabilityAlerts = await githubApi.queryRepoField<{ - node: VulnerabilityAlert; - }>(query, 'vulnerabilityAlerts', { - variables: { owner: config.repositoryOwner, name: config.repositoryName }, - paginate: false, - acceptHeader: 'application/vnd.github.vixen-preview+json', - readOnly: true, - }); + vulnerabilityAlerts = ( + await githubApi.getJson( + `/repos/${config.repositoryOwner}/${config.repositoryName}/dependabot/alerts?state=open&direction=asc&per_page=100`, + { + paginate: false, + headers: { accept: 'application/vnd.github+json' }, + cacheProvider: repoCacheProvider, + }, + VulnerabilityAlertSchema, + ) + ).body; } catch (err) { logger.debug({ err }, 'Error retrieving vulnerability alerts'); logger.warn( @@ -1990,42 +1987,43 @@ export async function getVulnerabilityAlerts(): Promise { 'Cannot access vulnerability alerts. Please ensure permissions have been granted.', ); } - let alerts: VulnerabilityAlert[] = []; try { if (vulnerabilityAlerts?.length) { - alerts = vulnerabilityAlerts.map((edge) => edge.node); const shortAlerts: AggregatedVulnerabilities = {}; - if (alerts.length) { - logger.trace({ alerts }, 'GitHub vulnerability details'); - for (const alert of alerts) { - if (alert.securityVulnerability === null) { - // As described in the documentation, there are cases in which - // GitHub API responds with `"securityVulnerability": null`. - // But it's may be faulty, so skip processing it here. - continue; - } - const { - package: { name, ecosystem }, - vulnerableVersionRange, - firstPatchedVersion, - } = alert.securityVulnerability; - const patch = firstPatchedVersion?.identifier; - - const key = `${ecosystem.toLowerCase()}/${name}`; - const range = vulnerableVersionRange; - const elem = shortAlerts[key] || {}; - elem[range] = coerceToNull(patch); - shortAlerts[key] = elem; + logger.trace( + { alerts: vulnerabilityAlerts }, + 'GitHub vulnerability details', + ); + for (const alert of vulnerabilityAlerts) { + if (alert.security_vulnerability === null) { + // As described in the documentation, there are cases in which + // GitHub API responds with `"securityVulnerability": null`. + // But it's may be faulty, so skip processing it here. + continue; } - logger.debug({ alerts: shortAlerts }, 'GitHub vulnerability details'); + const { + package: { name, ecosystem }, + vulnerable_version_range: vulnerableVersionRange, + first_patched_version: firstPatchedVersion, + } = alert.security_vulnerability; + const patch = firstPatchedVersion?.identifier; + + const normalizedName = normalizeNamePerEcosystem({ name, ecosystem }); + alert.security_vulnerability.package.name = normalizedName; + const key = `${ecosystem.toLowerCase()}/${normalizedName}`; + const range = vulnerableVersionRange; + const elem = shortAlerts[key] || {}; + elem[range] = coerceToNull(patch); + shortAlerts[key] = elem; } + logger.debug({ alerts: shortAlerts }, 'GitHub vulnerability details'); } else { logger.debug('No vulnerability alerts found'); } } catch (err) /* istanbul ignore next */ { logger.error({ err }, 'Error processing vulnerabity alerts'); } - return alerts; + return vulnerabilityAlerts ?? []; } async function pushFiles( diff --git a/lib/modules/platform/github/schema.ts b/lib/modules/platform/github/schema.ts new file mode 100644 index 00000000000000..01b867695ab929 --- /dev/null +++ b/lib/modules/platform/github/schema.ts @@ -0,0 +1,55 @@ +import { z } from 'zod'; +import { logger } from '../../../logger'; +import { LooseArray } from '../../../util/schema-utils'; + +const PackageSchema = z.object({ + ecosystem: z.union([ + z.literal('maven'), + z.literal('npm'), + z.literal('nuget'), + z.literal('pip'), + z.literal('rubygems'), + z.literal('rust'), + z.literal('composer'), + z.literal('go'), + ]), + name: z.string(), +}); + +const SecurityVulnerabilitySchema = z + .object({ + first_patched_version: z.object({ identifier: z.string() }).optional(), + package: PackageSchema, + vulnerable_version_range: z.string(), + }) + .nullable(); + +const SecurityAdvisorySchema = z.object({ + description: z.string(), + identifiers: z.array( + z.object({ + type: z.string(), + value: z.string(), + }), + ), + references: z.array(z.object({ url: z.string() })).optional(), +}); + +export const VulnerabilityAlertSchema = LooseArray( + z.object({ + dismissed_reason: z.string().nullish(), + security_advisory: SecurityAdvisorySchema, + security_vulnerability: SecurityVulnerabilitySchema, + dependency: z.object({ + manifest_path: z.string(), + }), + }), + { + onError: /* istanbul ignore next */ ({ error }) => { + logger.debug( + { error }, + 'Vulnerability Alert: Failed to parse some alerts', + ); + }, + }, +); diff --git a/lib/modules/platform/github/scm.spec.ts b/lib/modules/platform/github/scm.spec.ts index f4b8c432ba9fb3..7e25cde834b083 100644 --- a/lib/modules/platform/github/scm.spec.ts +++ b/lib/modules/platform/github/scm.spec.ts @@ -20,29 +20,57 @@ describe('modules/platform/github/scm', () => { message: 'msg', } satisfies CommitFilesConfig; - it('platformCommit = false => delegate to git', async () => { + it('platformCommit = disabled => delegate to git', async () => { await githubScm.commitAndPush({ ...commitObj, - platformCommit: false, + platformCommit: 'disabled', }); expect(git.commitFiles).toHaveBeenCalledWith({ ...commitObj, - platformCommit: false, + platformCommit: 'disabled', }); expect(github.commitFiles).not.toHaveBeenCalled(); }); - it('platformCommit = true => delegate to github', async () => { + it('platformCommit = enabled => delegate to github', async () => { await githubScm.commitAndPush({ ...commitObj, - platformCommit: true, + platformCommit: 'enabled', }); expect(git.commitFiles).not.toHaveBeenCalled(); expect(github.commitFiles).toHaveBeenCalledWith({ ...commitObj, - platformCommit: true, + platformCommit: 'enabled', + }); + }); + + it('platformCommit = auto => delegate to git', async () => { + await githubScm.commitAndPush({ + ...commitObj, + platformCommit: 'auto', + }); + + expect(git.commitFiles).toHaveBeenCalledWith({ + ...commitObj, + platformCommit: 'auto', + }); + expect(github.commitFiles).not.toHaveBeenCalled(); + }); + + it('platformCommit = auto and is a github app => delegate to github', async () => { + github.isGHApp.mockReturnValueOnce(true); + + await githubScm.commitAndPush({ + ...commitObj, + platformCommit: 'auto', + }); + + expect(git.commitFiles).not.toHaveBeenCalled(); + expect(github.commitFiles).toHaveBeenCalledWith({ + ...commitObj, + platformCommit: 'auto', }); }); }); diff --git a/lib/modules/platform/github/scm.ts b/lib/modules/platform/github/scm.ts index aad764581884b2..cb943b46412fa6 100644 --- a/lib/modules/platform/github/scm.ts +++ b/lib/modules/platform/github/scm.ts @@ -1,13 +1,18 @@ import * as git from '../../../util/git'; import type { CommitFilesConfig, LongCommitSha } from '../../../util/git/types'; import { DefaultGitScm } from '../default-scm'; -import { commitFiles } from './'; +import { commitFiles, isGHApp } from './'; export class GithubScm extends DefaultGitScm { override commitAndPush( commitConfig: CommitFilesConfig, ): Promise { - return commitConfig.platformCommit + let platformCommit = commitConfig.platformCommit; + if (platformCommit === 'auto' && isGHApp()) { + platformCommit = 'enabled'; + } + + return platformCommit === 'enabled' ? commitFiles(commitConfig) : git.commitFiles(commitConfig); } diff --git a/lib/modules/platform/local/scm.spec.ts b/lib/modules/platform/local/scm.spec.ts index 2b5b0c12ca4d41..e96eee93fef824 100644 --- a/lib/modules/platform/local/scm.spec.ts +++ b/lib/modules/platform/local/scm.spec.ts @@ -21,7 +21,7 @@ describe('modules/platform/local/scm', () => { }); it('isBranchModified', async () => { - expect(await localFs.isBranchModified('')).toBe(false); + expect(await localFs.isBranchModified('', '')).toBe(false); }); it('isBranchConflicted', async () => { diff --git a/lib/modules/platform/local/scm.ts b/lib/modules/platform/local/scm.ts index b0af23bf5bdbe2..ed879f9ba6f6c5 100644 --- a/lib/modules/platform/local/scm.ts +++ b/lib/modules/platform/local/scm.ts @@ -9,7 +9,7 @@ export class LocalFs implements PlatformScm { isBranchBehindBase(branchName: string, baseBranch: string): Promise { return Promise.resolve(false); } - isBranchModified(branchName: string): Promise { + isBranchModified(branchName: string, baseBranch: string): Promise { return Promise.resolve(false); } isBranchConflicted(baseBranch: string, branch: string): Promise { diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index 28f622cd61a33c..301882fa7a80c5 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -281,7 +281,7 @@ export interface Platform { export interface PlatformScm { isBranchBehindBase(branchName: string, baseBranch: string): Promise; - isBranchModified(branchName: string): Promise; + isBranchModified(branchName: string, baseBranch: string): Promise; isBranchConflicted(baseBranch: string, branch: string): Promise; branchExists(branchName: string): Promise; getBranchCommit(branchName: string): Promise; diff --git a/lib/modules/platform/utils/github-alerts.ts b/lib/modules/platform/utils/github-alerts.ts new file mode 100644 index 00000000000000..b34a75e0947ed2 --- /dev/null +++ b/lib/modules/platform/utils/github-alerts.ts @@ -0,0 +1,14 @@ +import type { VulnerabilityPackage } from '../../../types'; +import { normalizePythonDepName } from '../../datasource/pypi/common'; + +export function normalizeNamePerEcosystem({ + name, + ecosystem, +}: VulnerabilityPackage): string { + switch (ecosystem) { + case 'pip': + return normalizePythonDepName(name); + default: + return name; + } +} diff --git a/lib/modules/versioning/regex/readme.md b/lib/modules/versioning/regex/readme.md index 2fe779d5015cde..b8512e5c95ef55 100644 --- a/lib/modules/versioning/regex/readme.md +++ b/lib/modules/versioning/regex/readme.md @@ -45,7 +45,7 @@ Here is another example, this time for handling Bitnami Docker images, which use "packageRules": [ { "matchDatasources": ["docker"], - "matchPackagePrefixes": ["bitnami/", "docker.io/bitnami/"], + "matchPackageNamees": ["bitnami/**", "docker.io/bitnami/**"], "versioning": "regex:^(?\\d+)\\.(?\\d+)\\.(?\\d+)(?:-(?.+)(?\\d+)-r(?\\d+))?$" } ] diff --git a/lib/types/host-rules.ts b/lib/types/host-rules.ts index 23ac43d7f64cbd..00820c81a80f02 100644 --- a/lib/types/host-rules.ts +++ b/lib/types/host-rules.ts @@ -14,7 +14,6 @@ export interface HostRule { headers?: Record; maxRetryAfter?: number; - dnsCache?: boolean; keepAlive?: boolean; artifactAuth?: string[] | null; httpsCertificateAuthority?: string; diff --git a/lib/types/vulnerability-alert.ts b/lib/types/vulnerability-alert.ts index 727f818f873c0f..5dcd261e8eecf8 100644 --- a/lib/types/vulnerability-alert.ts +++ b/lib/types/vulnerability-alert.ts @@ -1,25 +1,30 @@ export interface VulnerabilityPackage { - // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents - ecosystem: 'MAVEN' | 'NPM' | 'NUGET' | 'PIP' | 'RUBYGEMS' | string; + ecosystem: + | 'maven' + | 'npm' + | 'nuget' + | 'pip' + | 'rubygems' + | 'rust' + | 'composer' + | 'go'; name: string; } export interface SecurityVulnerability { - firstPatchedVersion?: { identifier: string }; + first_patched_version?: { identifier: string }; package: VulnerabilityPackage; - vulnerableVersionRange: string; + vulnerable_version_range: string; } export interface SecurityAdvisory { - description?: string; - identifiers?: { type: string; value: string }[]; - references: { url: string }[]; - // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents - severity: 'HIGH' | 'MODERATE' | string; + description: string; + identifiers: { type: string; value: string }[]; + references?: { url: string }[]; } export interface VulnerabilityAlert { - dismissReason?: string | null; - securityAdvisory: SecurityAdvisory; - securityVulnerability: SecurityVulnerability; - vulnerableManifestFilename: string; - vulnerableManifestPath: string; - vulnerableRequirements?: string; + dismissed_reason?: string | null; + security_advisory: SecurityAdvisory; + security_vulnerability: SecurityVulnerability | null; + dependency: { + manifest_path: string; + }; } diff --git a/lib/util/cache/repository/impl/s3.spec.ts b/lib/util/cache/repository/impl/s3.spec.ts index 438d2316a14e8b..cb2683c7ab5bf9 100644 --- a/lib/util/cache/repository/impl/s3.spec.ts +++ b/lib/util/cache/repository/impl/s3.spec.ts @@ -200,7 +200,7 @@ describe('util/cache/repository/impl/s3', () => { }); it('should persists data locally after uploading to s3', async () => { - process.env.RENOVATE_X_REPO_CACHE_FORCE_LOCAL = 'true'; + GlobalConfig.set({ experimentalFlags: ['repoCacheForceLocal'] }); const putObjectCommandOutput: PutObjectCommandOutput = { $metadata: { attempts: 1, httpStatusCode: 200, totalRetryDelay: 0 }, }; diff --git a/lib/util/cache/repository/impl/s3.ts b/lib/util/cache/repository/impl/s3.ts index e0cbe20bbb6451..3aac4dddac48ca 100644 --- a/lib/util/cache/repository/impl/s3.ts +++ b/lib/util/cache/repository/impl/s3.ts @@ -5,7 +5,7 @@ import { PutObjectCommand, PutObjectCommandInput, } from '@aws-sdk/client-s3'; -import is from '@sindresorhus/is'; +import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; import { outputCacheFile } from '../../../fs'; import { getS3Client, parseS3Url } from '../../../s3'; @@ -66,7 +66,7 @@ export class RepoCacheS3 extends RepoCacheBase { }; try { await this.s3Client.send(new PutObjectCommand(s3Params)); - if (is.nonEmptyString(process.env.RENOVATE_X_REPO_CACHE_FORCE_LOCAL)) { + if (GlobalConfig.getExperimentalFlag('repoCacheForceLocal')) { const cacheLocalFileName = getLocalCacheFileName( this.platform, this.repository, diff --git a/lib/util/exec/common.spec.ts b/lib/util/exec/common.spec.ts index 0e3f2a75a50aa1..bc6ec809f2916c 100644 --- a/lib/util/exec/common.spec.ts +++ b/lib/util/exec/common.spec.ts @@ -2,6 +2,7 @@ import { spawn as _spawn } from 'node:child_process'; import type { SendHandle, Serializable } from 'node:child_process'; import { Readable } from 'node:stream'; import { mockedFunction, partial } from '../../../test/util'; +import { GlobalConfig } from '../../config/global'; import { exec } from './common'; import type { RawExecOptions } from './types'; @@ -287,12 +288,12 @@ describe('util/exec/common', () => { const killSpy = jest.spyOn(process, 'kill'); afterEach(() => { - delete process.env.RENOVATE_X_EXEC_GPID_HANDLE; + GlobalConfig.reset(); jest.restoreAllMocks(); }); it('calls process.kill on the gpid', async () => { - process.env.RENOVATE_X_EXEC_GPID_HANDLE = 'true'; + GlobalConfig.set({ experimentalFlags: ['execGpidHandle'] }); const cmd = 'ls -l'; const exitSignal = 'SIGTERM'; const stub = getSpawnStub({ cmd, exitCode: null, exitSignal }); @@ -309,7 +310,7 @@ describe('util/exec/common', () => { }); it('handles process.kill call on non existent gpid', async () => { - process.env.RENOVATE_X_EXEC_GPID_HANDLE = 'true'; + GlobalConfig.set({ experimentalFlags: ['execGpidHandle'] }); const cmd = 'ls -l'; const exitSignal = 'SIGTERM'; const stub = getSpawnStub({ cmd, exitCode: null, exitSignal }); diff --git a/lib/util/exec/common.ts b/lib/util/exec/common.ts index 96dfa3c9294f26..8f8bda40900a69 100644 --- a/lib/util/exec/common.ts +++ b/lib/util/exec/common.ts @@ -1,4 +1,5 @@ import { ChildProcess, spawn } from 'node:child_process'; +import { GlobalConfig } from '../../config/global'; import { ExecError, ExecErrorData } from './exec-error'; import type { ExecResult, RawExecOptions } from './types'; @@ -122,7 +123,7 @@ export function exec(cmd: string, opts: RawExecOptions): Promise { function kill(cp: ChildProcess, signal: NodeJS.Signals): boolean { try { - if (cp.pid && process.env.RENOVATE_X_EXEC_GPID_HANDLE) { + if (cp.pid && GlobalConfig.getExperimentalFlag('execGpidHandle')) { /** * If `pid` is negative, but not `-1`, signal shall be sent to all processes * (excluding an unspecified set of system processes), diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts index b97aba5402c3c5..7bb0dcd2fc783f 100644 --- a/lib/util/git/index.spec.ts +++ b/lib/util/git/index.spec.ts @@ -87,6 +87,15 @@ describe('util/git/index', () => { await repo.checkoutBranch('renovate/equal_branch', defaultBranch); + await repo.checkoutBranch( + 'renovate/branch_with_multiple_authors', + defaultBranch, + ); + await repo.addConfig('user.email', 'author1@example.com'); + await repo.commit('first commit', undefined, { '--allow-empty': null }); + await repo.addConfig('user.email', 'author2@example.com'); + await repo.commit('second commit', undefined, { '--allow-empty': null }); + await repo.checkout(defaultBranch); }); @@ -327,28 +336,64 @@ describe('util/git/index', () => { }); it('should return false when branch is not found', async () => { - expect(await git.isBranchModified('renovate/not_found')).toBeFalse(); + expect( + await git.isBranchModified('renovate/not_found', defaultBranch), + ).toBeFalse(); }); it('should return false when author matches', async () => { - expect(await git.isBranchModified('renovate/future_branch')).toBeFalse(); - expect(await git.isBranchModified('renovate/future_branch')).toBeFalse(); + expect( + await git.isBranchModified('renovate/future_branch', defaultBranch), + ).toBeFalse(); + expect( + await git.isBranchModified('renovate/future_branch', defaultBranch), + ).toBeFalse(); }); it('should return false when author is ignored', async () => { git.setUserRepoConfig({ gitIgnoredAuthors: ['custom@example.com'], }); - expect(await git.isBranchModified('renovate/custom_author')).toBeFalse(); + expect( + await git.isBranchModified('renovate/custom_author', defaultBranch), + ).toBeFalse(); + }); + + it('should return true when non-ignored authors commit followed by an ignored author', async () => { + git.setUserRepoConfig({ + gitIgnoredAuthors: ['author1@example.com'], + }); + expect( + await git.isBranchModified( + 'renovate/branch_with_multiple_authors', + defaultBranch, + ), + ).toBeTrue(); + }); + + it('should return false with multiple authors that are each ignored', async () => { + git.setUserRepoConfig({ + gitIgnoredAuthors: ['author1@example.com', 'author2@example.com'], + }); + expect( + await git.isBranchModified( + 'renovate/branch_with_multiple_authors', + defaultBranch, + ), + ).toBeFalse(); }); it('should return true when custom author is unknown', async () => { - expect(await git.isBranchModified('renovate/custom_author')).toBeTrue(); + expect( + await git.isBranchModified('renovate/custom_author', defaultBranch), + ).toBeTrue(); }); it('should return value stored in modifiedCacheResult', async () => { modifiedCache.getCachedModifiedResult.mockReturnValue(true); - expect(await git.isBranchModified('renovate/future_branch')).toBeTrue(); + expect( + await git.isBranchModified('renovate/future_branch', defaultBranch), + ).toBeTrue(); }); }); diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index 5900222c072c11..292ab530bff8e0 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -628,7 +628,10 @@ export async function isBranchBehindBase( } } -export async function isBranchModified(branchName: string): Promise { +export async function isBranchModified( + branchName: string, + baseBranch: string, +): Promise { if (!branchExists(branchName)) { logger.debug('branch.isModified(): no cache'); return false; @@ -651,18 +654,15 @@ export async function isBranchModified(branchName: string): Promise { logger.debug('branch.isModified(): using git to calculate'); await syncGit(); - // Retrieve the author of the most recent commit - let lastAuthor: string | undefined; + const committedAuthors = new Set(); try { - lastAuthor = ( - await git.raw([ - 'log', - '-1', - '--pretty=format:%ae', - `origin/${branchName}`, - '--', - ]) - ).trim(); + const commits = await git.log([ + `origin/${baseBranch}..origin/${branchName}`, + ]); + + for (const commit of commits.all) { + committedAuthors.add(commit.author_email); + } } catch (err) /* istanbul ignore next */ { if (err.message?.includes('fatal: bad revision')) { logger.debug( @@ -673,21 +673,48 @@ export async function isBranchModified(branchName: string): Promise { } logger.warn({ err }, 'Error checking last author for isBranchModified'); } - const { gitAuthorEmail } = config; - if ( - lastAuthor === gitAuthorEmail || - config.ignoredAuthors.some((ignoredAuthor) => lastAuthor === ignoredAuthor) - ) { - // author matches - branch has not been modified + const { gitAuthorEmail, ignoredAuthors } = config; + + const includedAuthors = new Set(committedAuthors); + + if (gitAuthorEmail) { + includedAuthors.delete(gitAuthorEmail); + } + + for (const ignoredAuthor of ignoredAuthors) { + includedAuthors.delete(ignoredAuthor); + } + + if (includedAuthors.size === 0) { + // authors all match - branch has not been modified + logger.trace( + { + branchName, + baseBranch, + committedAuthors: [...committedAuthors], + includedAuthors: [...includedAuthors], + gitAuthorEmail, + ignoredAuthors, + }, + 'branch.isModified() = false', + ); logger.debug('branch.isModified() = false'); config.branchIsModified[branchName] = false; setCachedModifiedResult(branchName, false); return false; } - logger.debug( - { branchName, lastAuthor, gitAuthorEmail }, + logger.trace( + { + branchName, + baseBranch, + committedAuthors: [...committedAuthors], + includedAuthors: [...includedAuthors], + gitAuthorEmail, + ignoredAuthors, + }, 'branch.isModified() = true', ); + logger.debug('branch.isModified() = true'); config.branchIsModified[branchName] = true; setCachedModifiedResult(branchName, true); return true; diff --git a/lib/util/git/types.ts b/lib/util/git/types.ts index 0dd48c4274b55f..2dd36b64085a12 100644 --- a/lib/util/git/types.ts +++ b/lib/util/git/types.ts @@ -1,3 +1,4 @@ +import type { PlatformCommitOptions } from '../../config/types'; import type { GitOptions } from '../../types/git'; export type { DiffResult, StatusResult } from 'simple-git'; @@ -81,7 +82,7 @@ export interface CommitFilesConfig { files: FileChange[]; message: string | string[]; force?: boolean; - platformCommit?: boolean; + platformCommit?: PlatformCommitOptions; } export interface PushFilesConfig { diff --git a/lib/util/http/auth.spec.ts b/lib/util/http/auth.spec.ts index 2854af307c1389..208a8209b67da1 100644 --- a/lib/util/http/auth.spec.ts +++ b/lib/util/http/auth.spec.ts @@ -57,7 +57,7 @@ describe('util/http/auth', () => { expect(opts).toMatchInlineSnapshot(` { "headers": { - "authorization": "token XXXX", + "authorization": "Bearer XXXX", }, "hostType": "gitea", "token": "XXXX", diff --git a/lib/util/http/auth.ts b/lib/util/http/auth.ts index 99cc9bec817651..0beec921d901c9 100644 --- a/lib/util/http/auth.ts +++ b/lib/util/http/auth.ts @@ -40,7 +40,9 @@ export function applyAuthorization( options.hostType && GITEA_API_USING_HOST_TYPES.includes(options.hostType) ) { - options.headers.authorization = `token ${options.token}`; + // Gitea v1.8.0 and later support `Bearer` as alternate to `token` + // https://github.com/go-gitea/gitea/pull/5378 + options.headers.authorization = `Bearer ${options.token}`; } else if ( options.hostType && GITHUB_API_USING_HOST_TYPES.includes(options.hostType) diff --git a/lib/util/http/dns.spec.ts b/lib/util/http/dns.spec.ts deleted file mode 100644 index db574bc10e2f2a..00000000000000 --- a/lib/util/http/dns.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { logger } from '../../logger'; -import { clearDnsCache, dnsLookup, printDnsStats } from './dns'; - -describe('util/http/dns', () => { - describe('dnsLookup', () => { - it('works', async () => { - clearDnsCache(); - const ip = await new Promise((resolve) => - dnsLookup('api.github.com', 4, (_e, r, _f) => { - resolve(r); - }), - ); - expect(ip).toBeString(); - // uses cache - expect( - await new Promise((resolve) => - dnsLookup('api.github.com', (_e, r, _f) => { - resolve(r); - }), - ), - ).toBe(ip); - expect( - await new Promise((resolve) => - dnsLookup('api.github.com', {}, (_e, r, _f) => { - resolve(r); - }), - ), - ).toBe(ip); - }); - - it('throws', async () => { - clearDnsCache(); - const ip = new Promise((resolve, reject) => - dnsLookup('api.github.comcccccccc', 4, (_e, r, _f) => { - if (_e) { - reject(_e); - } else { - resolve(r); - } - }), - ); - await expect(ip).rejects.toThrow(); - }); - - it('prints stats', () => { - printDnsStats(); - expect(logger.debug).toHaveBeenCalled(); - }); - }); -}); diff --git a/lib/util/http/dns.ts b/lib/util/http/dns.ts deleted file mode 100644 index 808060f0d0de33..00000000000000 --- a/lib/util/http/dns.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - LookupAllOptions, - LookupOneOptions, - lookup as _dnsLookup, -} from 'node:dns'; -import type { EntryObject, IPFamily, LookupOptions } from 'cacheable-lookup'; -import { LRUCache } from 'lru-cache'; -import { logger } from '../../logger'; - -const cache = new LRUCache({ max: 1000 }); - -function lookup( - ...[host, options, callback]: - | [ - hostname: string, - family: IPFamily, - callback: ( - error: NodeJS.ErrnoException, - address: string, - family: IPFamily, - ) => void, - ] - | [ - hostname: string, - callback: ( - error: NodeJS.ErrnoException, - address: string, - family: IPFamily, - ) => void, - ] - | [ - hostname: string, - options: LookupOptions & { all: true }, - callback: ( - error: NodeJS.ErrnoException, - result: ReadonlyArray, - ) => void, - ] - | [ - hostname: string, - options: LookupOptions, - callback: ( - error: NodeJS.ErrnoException, - address: string, - family: IPFamily, - ) => void, - ] -): void { - let opts: LookupOneOptions | LookupAllOptions; - // TODO: strict null incompatible types (#22198) - let cb: any; - - if (typeof options === 'function') { - opts = {}; - cb = options; - } else if (typeof options === 'number') { - opts = { family: options }; - cb = callback; - } else { - opts = options; - cb = callback; - } - - // istanbul ignore if: not used - if (opts.all) { - const key = `${host}_all`; - if (cache.has(key)) { - logger.trace({ host }, 'dns lookup cache hit'); - cb(null, cache.get(key)); - return; - } - - _dnsLookup(host, opts, (err, res) => { - if (err) { - logger.debug({ host, err }, 'dns lookup error'); - cb(err, null, null); - return; - } - logger.trace({ host, opts, res }, 'dns lookup'); - cache.set(key, res); - cb(null, res, null); - }); - } else { - if (cache.has(host)) { - logger.trace({ host }, 'dns lookup cache hit'); - cb(null, ...cache.get(host)); - return; - } - - _dnsLookup(host, opts, (err, ...res) => { - if (err) { - logger.debug({ host, err }, 'dns lookup error'); - cb(err); - return; - } - logger.trace({ host, opts, res }, 'dns lookup'); - cache.set(host, res); - cb(null, ...res); - }); - } -} - -export { lookup as dnsLookup }; - -export function printDnsStats(): void { - logger.debug({ hosts: Array.from(cache.keys()) }, 'dns cache'); -} - -export function clearDnsCache(): void { - cache.clear(); -} diff --git a/lib/util/http/host-rules.spec.ts b/lib/util/http/host-rules.spec.ts index 2b452132838578..dcbaa06de70a4e 100644 --- a/lib/util/http/host-rules.spec.ts +++ b/lib/util/http/host-rules.spec.ts @@ -2,7 +2,6 @@ import { GlobalConfig } from '../../config/global'; import { bootstrap } from '../../proxy'; import type { HostRule } from '../../types'; import * as hostRules from '../host-rules'; -import { dnsLookup } from './dns'; import { applyHostRule, findMatchingRule } from './host-rules'; import type { GotOptions } from './types'; @@ -125,22 +124,6 @@ describe('util/http/host-rules', () => { }); }); - it('uses dnsCache', () => { - hostRules.add({ dnsCache: true }); - - const opts = { ...options, token: 'xxx' }; - const hostRule = findMatchingRule(url, opts); - expect(hostRule).toEqual({ - dnsCache: true, - token: 'token', - }); - expect(applyHostRule(url, opts, hostRule)).toMatchObject({ - hostType: 'github', - lookup: dnsLookup, - token: 'xxx', - }); - }); - it('uses http keep-alive', () => { hostRules.add({ keepAlive: true }); diff --git a/lib/util/http/host-rules.ts b/lib/util/http/host-rules.ts index 09775fe9f412fe..2bdbbcebfc3d7f 100644 --- a/lib/util/http/host-rules.ts +++ b/lib/util/http/host-rules.ts @@ -12,7 +12,6 @@ import type { HostRule } from '../../types'; import * as hostRules from '../host-rules'; import { matchRegexOrGlobList } from '../string-match'; import { parseUrl } from '../url'; -import { dnsLookup } from './dns'; import { keepAliveAgents } from './keep-alive'; import type { GotOptions, InternalHttpOptions } from './types'; @@ -161,10 +160,6 @@ export function applyHostRule( options.timeout = hostRule.timeout; } - if (hostRule.dnsCache) { - options.lookup = dnsLookup; - } - if (hostRule.headers) { const allowedHeaders = GlobalConfig.get('allowedHeaders', []); const filteredHeaders: Record = {}; diff --git a/lib/util/package-rules/base-branches.ts b/lib/util/package-rules/base-branches.ts index 8e8cb2eb911941..71bf07b36408f3 100644 --- a/lib/util/package-rules/base-branches.ts +++ b/lib/util/package-rules/base-branches.ts @@ -1,6 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { getRegexPredicate } from '../string-match'; +import { matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class BaseBranchesMatcher extends Matcher { @@ -16,12 +16,6 @@ export class BaseBranchesMatcher extends Matcher { return false; } - return matchBaseBranches.some((matchBaseBranch): boolean => { - const isAllowedPred = getRegexPredicate(matchBaseBranch); - if (isAllowedPred) { - return isAllowedPred(baseBranch); - } - return matchBaseBranch === baseBranch; - }); + return matchRegexOrGlobList(baseBranch, matchBaseBranches); } } diff --git a/lib/util/package-rules/base.ts b/lib/util/package-rules/base.ts index 981a11575335db..121a982a7fc40d 100644 --- a/lib/util/package-rules/base.ts +++ b/lib/util/package-rules/base.ts @@ -2,19 +2,6 @@ import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; import type { MatcherApi } from './types'; export abstract class Matcher implements MatcherApi { - /** - * Test exclusion packageRule against inputConfig - * @return null if no rules are defined, true if exclusion should be applied and else false - * @param inputConfig - * @param packageRule - */ - excludes( - inputConfig: PackageRuleInputConfig, - packageRule: PackageRule, - ): boolean | null { - return null; - } - /** * Test match packageRule against inputConfig * @return null if no rules are defined, true if match should be applied and else false diff --git a/lib/util/package-rules/categories.ts b/lib/util/package-rules/categories.ts index 0a5e47ae304580..b26a8e2ac80391 100644 --- a/lib/util/package-rules/categories.ts +++ b/lib/util/package-rules/categories.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; +import { anyMatchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class CategoriesMatcher extends Matcher { @@ -15,6 +16,6 @@ export class CategoriesMatcher extends Matcher { return false; } - return matchCategories.some((value) => categories.includes(value)); + return anyMatchRegexOrGlobList(categories, matchCategories); } } diff --git a/lib/util/package-rules/datasources.ts b/lib/util/package-rules/datasources.ts index 3e6b5bef446e51..5fbfeca59481d6 100644 --- a/lib/util/package-rules/datasources.ts +++ b/lib/util/package-rules/datasources.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; +import { matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class DatasourcesMatcher extends Matcher { @@ -13,6 +14,6 @@ export class DatasourcesMatcher extends Matcher { if (is.undefined(datasource)) { return false; } - return matchDatasources.includes(datasource); + return matchRegexOrGlobList(datasource, matchDatasources); } } diff --git a/lib/util/package-rules/dep-names.spec.ts b/lib/util/package-rules/dep-names.spec.ts index eb0bea92f2584e..3a97c5696e40d3 100644 --- a/lib/util/package-rules/dep-names.spec.ts +++ b/lib/util/package-rules/dep-names.spec.ts @@ -16,18 +16,4 @@ describe('util/package-rules/dep-names', () => { expect(result).toBeFalse(); }); }); - - describe('exclude', () => { - it('should return false if packageFile is not defined', () => { - const result = depNameMatcher.excludes( - { - depName: undefined, - }, - { - excludeDepNames: ['@opentelemetry/http'], - }, - ); - expect(result).toBeFalse(); - }); - }); }); diff --git a/lib/util/package-rules/dep-names.ts b/lib/util/package-rules/dep-names.ts index d745863ea4d2f5..0b808de7984fc0 100644 --- a/lib/util/package-rules/dep-names.ts +++ b/lib/util/package-rules/dep-names.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; +import { matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class DepNameMatcher extends Matcher { @@ -13,19 +14,6 @@ export class DepNameMatcher extends Matcher { if (is.undefined(depName)) { return false; } - return matchDepNames.includes(depName); - } - - override excludes( - { depName }: PackageRuleInputConfig, - { excludeDepNames }: PackageRule, - ): boolean | null { - if (is.undefined(excludeDepNames)) { - return null; - } - if (is.undefined(depName)) { - return false; - } - return excludeDepNames.includes(depName); + return matchRegexOrGlobList(depName, matchDepNames); } } diff --git a/lib/util/package-rules/dep-patterns.spec.ts b/lib/util/package-rules/dep-patterns.spec.ts deleted file mode 100644 index 0f976a179bf9bf..00000000000000 --- a/lib/util/package-rules/dep-patterns.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { DepPatternsMatcher } from './dep-patterns'; - -describe('util/package-rules/dep-patterns', () => { - const depPatternsMatcher = new DepPatternsMatcher(); - - describe('match', () => { - it('should return false if depName is not defined', () => { - const result = depPatternsMatcher.matches( - { - depName: undefined, - }, - { - matchDepPatterns: ['@opentelemetry/http'], - }, - ); - expect(result).toBeFalse(); - }); - }); - - describe('exclude', () => { - it('should return false if depName is not defined', () => { - const result = depPatternsMatcher.excludes( - { - depName: undefined, - }, - { - excludeDepPatterns: ['@opentelemetry/http'], - }, - ); - expect(result).toBeFalse(); - }); - }); -}); diff --git a/lib/util/package-rules/dep-patterns.ts b/lib/util/package-rules/dep-patterns.ts deleted file mode 100644 index 7a566a726f991f..00000000000000 --- a/lib/util/package-rules/dep-patterns.ts +++ /dev/null @@ -1,54 +0,0 @@ -import is from '@sindresorhus/is'; -import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { logger } from '../../logger'; -import { regEx } from '../regex'; -import { Matcher } from './base'; -import { massagePattern } from './utils'; - -export class DepPatternsMatcher extends Matcher { - override matches( - { depName, updateType }: PackageRuleInputConfig, - { matchDepPatterns }: PackageRule, - ): boolean | null { - if (is.undefined(matchDepPatterns)) { - return null; - } - - if (is.undefined(depName)) { - return false; - } - - let isMatch = false; - for (const packagePattern of matchDepPatterns) { - const packageRegex = regEx(massagePattern(packagePattern)); - if (packageRegex.test(depName)) { - logger.trace(`${depName} matches against ${String(packageRegex)}`); - isMatch = true; - } - } - return isMatch; - } - - override excludes( - { depName, updateType }: PackageRuleInputConfig, - { excludeDepPatterns }: PackageRule, - ): boolean | null { - // ignore lockFileMaintenance for backwards compatibility - if (is.undefined(excludeDepPatterns)) { - return null; - } - if (is.undefined(depName)) { - return false; - } - - let isMatch = false; - for (const pattern of excludeDepPatterns) { - const packageRegex = regEx(massagePattern(pattern)); - if (packageRegex.test(depName)) { - logger.trace(`${depName} matches against ${String(packageRegex)}`); - isMatch = true; - } - } - return isMatch; - } -} diff --git a/lib/util/package-rules/dep-prefixes.spec.ts b/lib/util/package-rules/dep-prefixes.spec.ts deleted file mode 100644 index d5b35da075989d..00000000000000 --- a/lib/util/package-rules/dep-prefixes.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { DepPrefixesMatcher } from './dep-prefixes'; - -describe('util/package-rules/dep-prefixes', () => { - const depPrefixesMatcher = new DepPrefixesMatcher(); - - describe('match', () => { - it('should return null if matchDepPrefixes is not defined', () => { - const result = depPrefixesMatcher.matches( - { - depName: 'abc1', - }, - { - matchDepPrefixes: undefined, - }, - ); - expect(result).toBeNull(); - }); - - it('should return false if depName is not defined', () => { - const result = depPrefixesMatcher.matches( - { - depName: undefined, - }, - { - matchDepPrefixes: ['@opentelemetry'], - }, - ); - expect(result).toBeFalse(); - }); - - it('should return true if depName matched', () => { - const result = depPrefixesMatcher.matches( - { - depName: 'abc1', - }, - { - matchDepPrefixes: ['abc'], - }, - ); - expect(result).toBeTrue(); - }); - - it('should return false if depName does not match', () => { - const result = depPrefixesMatcher.matches( - { - depName: 'abc1', - }, - { - matchDepPrefixes: ['def'], - }, - ); - expect(result).toBeFalse(); - }); - }); - - describe('exclude', () => { - it('should return null if excludeDepPrefixes is not defined', () => { - const result = depPrefixesMatcher.excludes( - { - depName: 'abc1', - }, - { - excludeDepPrefixes: undefined, - }, - ); - expect(result).toBeNull(); - }); - - it('should return false if depName is not defined', () => { - const result = depPrefixesMatcher.excludes( - { - depName: undefined, - }, - { - excludeDepPrefixes: ['@opentelemetry'], - }, - ); - expect(result).toBeFalse(); - }); - - it('should return true if depName matched', () => { - const result = depPrefixesMatcher.excludes( - { - depName: 'abc1', - }, - { - excludeDepPrefixes: ['abc'], - }, - ); - expect(result).toBeTrue(); - }); - - it('should return false if depName does not match', () => { - const result = depPrefixesMatcher.excludes( - { - depName: 'abc1', - }, - { - excludeDepPrefixes: ['def'], - }, - ); - expect(result).toBeFalse(); - }); - }); -}); diff --git a/lib/util/package-rules/dep-prefixes.ts b/lib/util/package-rules/dep-prefixes.ts deleted file mode 100644 index 351df41f8605e5..00000000000000 --- a/lib/util/package-rules/dep-prefixes.ts +++ /dev/null @@ -1,35 +0,0 @@ -import is from '@sindresorhus/is'; -import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { Matcher } from './base'; - -export class DepPrefixesMatcher extends Matcher { - override matches( - { depName }: PackageRuleInputConfig, - { matchDepPrefixes }: PackageRule, - ): boolean | null { - if (is.undefined(matchDepPrefixes)) { - return null; - } - - if (is.undefined(depName)) { - return false; - } - - return matchDepPrefixes.some((prefix) => depName.startsWith(prefix)); - } - - override excludes( - { depName }: PackageRuleInputConfig, - { excludeDepPrefixes }: PackageRule, - ): boolean | null { - if (is.undefined(excludeDepPrefixes)) { - return null; - } - - if (is.undefined(depName)) { - return false; - } - - return excludeDepPrefixes.some((prefix) => depName.startsWith(prefix)); - } -} diff --git a/lib/util/package-rules/dep-types.ts b/lib/util/package-rules/dep-types.ts index 73a6087c96f8c2..434e81523dbefd 100644 --- a/lib/util/package-rules/dep-types.ts +++ b/lib/util/package-rules/dep-types.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; +import { anyMatchRegexOrGlobList, matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class DepTypesMatcher extends Matcher { @@ -11,9 +12,14 @@ export class DepTypesMatcher extends Matcher { return null; } - const result = - (is.string(depType) && matchDepTypes.includes(depType)) || - depTypes?.some((dt) => matchDepTypes.includes(dt)); - return result ?? false; + if (depType) { + return matchRegexOrGlobList(depType, matchDepTypes); + } + + if (depTypes) { + return anyMatchRegexOrGlobList(depTypes, matchDepTypes); + } + + return false; } } diff --git a/lib/util/package-rules/files.ts b/lib/util/package-rules/files.ts index 48b69c999181de..73151dbb07af80 100644 --- a/lib/util/package-rules/files.ts +++ b/lib/util/package-rules/files.ts @@ -1,6 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { minimatch } from '../minimatch'; +import { anyMatchRegexOrGlobList, matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class FileNamesMatcher extends Matcher { @@ -15,13 +15,14 @@ export class FileNamesMatcher extends Matcher { return false; } - return matchFileNames.some( - (matchFileName) => - minimatch(matchFileName, { dot: true }).match(packageFile) || - (is.array(lockFiles) && - lockFiles.some((lockFile) => - minimatch(matchFileName, { dot: true }).match(lockFile), - )), - ); + if (matchRegexOrGlobList(packageFile, matchFileNames)) { + return true; + } + + if (is.array(lockFiles)) { + return anyMatchRegexOrGlobList(lockFiles, matchFileNames); + } + + return false; } } diff --git a/lib/util/package-rules/index.spec.ts b/lib/util/package-rules/index.spec.ts index b47ca9e99b877f..a2b9a293152f95 100644 --- a/lib/util/package-rules/index.spec.ts +++ b/lib/util/package-rules/index.spec.ts @@ -19,20 +19,15 @@ describe('util/package-rules/index', () => { packageRules: [ { - matchPackageNames: ['a', 'b'], - matchPackagePrefixes: ['xyz/'], - excludePackagePrefixes: ['xyz/foo'], + matchPackageNames: ['a', 'b', 'xyz/**', '!xyz/foo**'], x: 2, }, { - matchPackagePatterns: ['a', 'b'], - excludePackageNames: ['aa'], - excludePackagePatterns: ['d'], + matchPackageNames: ['/a/', '/b/', '!aa', '!/d/'], y: 2, }, { - matchPackagePrefixes: ['xyz/'], - excludePackageNames: ['xyz/foo'], + matchPackageNames: ['xyz/**', '!xyz/foo'], groupName: 'xyz', }, ], @@ -40,28 +35,25 @@ describe('util/package-rules/index', () => { it('applies', () => { const config: PackageRuleInputConfig = { - depName: 'a', + packageName: 'a', + updateType: 'minor', isBump: true, currentValue: '1.0.0', packageRules: [ { - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], matchCurrentVersion: '<= 2.0.0', }, { matchPackageNames: ['b'], matchCurrentVersion: '<= 2.0.0', }, - { - excludePackagePatterns: ['*'], - }, { matchUpdateTypes: ['bump'], labels: ['bump'], }, { - excludePackageNames: ['a'], - matchPackageNames: ['b'], + matchPackageNames: ['b', '!a'], }, { matchCurrentVersion: '<= 2.0.0', @@ -76,7 +68,7 @@ describe('util/package-rules/index', () => { it('applies both rules for a', () => { const dep = { - depName: 'a', + packageName: 'a', }; const res = applyPackageRules({ ...config1, ...dep }); expect(res.x).toBe(2); @@ -86,7 +78,7 @@ describe('util/package-rules/index', () => { it('applies both rules for b', () => { const dep = { - depName: 'b', + packageName: 'b', }; const res = applyPackageRules({ ...config1, ...dep }); expect(res.x).toBe(2); @@ -96,7 +88,7 @@ describe('util/package-rules/index', () => { it('applies the second rule', () => { const dep = { - depName: 'abc', + packageName: 'abc', }; const res = applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); @@ -104,36 +96,17 @@ describe('util/package-rules/index', () => { expect(res.groupName).toBeUndefined(); }); - it('applies matchPackagePrefixes', () => { + it('applies matchPackageNames', () => { const dep = { - depName: 'xyz/abc', - }; - const res = applyPackageRules({ ...config1, ...dep }); - expect(res.x).toBe(2); - expect(res.y).toBe(2); - expect(res.groupName).toBe('xyz'); - }); - - it('applies excludePackageNames', () => { - const dep = { - depName: 'xyz/foo', + packageName: 'xyz/foo', }; const res = applyPackageRules({ ...config1, ...dep }); expect(res.groupName).toBeUndefined(); }); - it('applies excludePackagePrefixes', () => { - const dep = { - depName: 'xyz/foo-a', - }; - const res = applyPackageRules({ ...config1, ...dep }); - expect(res.x).toBeUndefined(); - expect(res.groupName).toBe('xyz'); - }); - it('applies the second second rule', () => { const dep = { - depName: 'bc', + packageName: 'bc', }; const res = applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); @@ -142,7 +115,7 @@ describe('util/package-rules/index', () => { it('excludes package name', () => { const dep = { - depName: 'aa', + packageName: 'aa', }; const res = applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); @@ -151,7 +124,7 @@ describe('util/package-rules/index', () => { it('excludes package pattern', () => { const dep = { - depName: 'bcd', + packageName: 'bcd', }; const res = applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); @@ -164,33 +137,14 @@ describe('util/package-rules/index', () => { updateType: 'lockFileMaintenance' as UpdateType, packageRules: [ { - excludePackagePatterns: ['^foo'], - automerge: false, - }, - ], - }; - const res = applyPackageRules(dep); - expect(res.automerge).toBeFalse(); - const res2 = applyPackageRules({ ...dep, depName: 'foo' }); - expect(res2.automerge).toBeTrue(); - }); - - it('do not apply rule with empty matchPackagePattern', () => { - const dep = { - automerge: true, - updateType: 'lockFileMaintenance' as UpdateType, - packageRules: [ - { - matchPackagePatterns: [], - excludePackagePatterns: ['^foo'], + matchPackageNames: ['!/^foo/'], automerge: false, }, ], }; + // This should not match const res = applyPackageRules(dep); expect(res.automerge).toBeTrue(); - const res2 = applyPackageRules({ ...dep, depName: 'foo' }); - expect(res2.automerge).toBeTrue(); }); it('do apply rule with matchPackageName', () => { @@ -206,55 +160,27 @@ describe('util/package-rules/index', () => { }; const res = applyPackageRules(dep); expect(res.automerge).toBeTrue(); - const res2 = applyPackageRules({ ...dep, depName: 'foo' }); + const res2 = applyPackageRules({ ...dep, packageName: 'foo' }); expect(res2.automerge).toBeFalse(); }); - it('sets skipReason=package-rules if enabled=false', () => { - const dep: any = { - depName: 'foo', - packageRules: [ - { - enabled: false, - }, - ], - }; - const res = applyPackageRules(dep); - expect(res.enabled).toBeFalse(); - expect(res.skipReason).toBe('package-rules'); - }); - - it('skips skipReason=package-rules if enabled=true', () => { - const dep: any = { - enabled: false, - depName: 'foo', - packageRules: [ - { - enabled: false, - }, - ], - }; - const res = applyPackageRules(dep); - expect(res.skipReason).toBeUndefined(); - }); - it('matches anything if missing inclusive rules', () => { const config: TestConfig = { packageRules: [ { - excludePackageNames: ['foo'], + matchPackageNames: ['!foo'], x: 1, }, ], }; const res1 = applyPackageRules({ ...config, - depName: 'foo', + packageName: 'foo', }); expect(res1.x).toBeUndefined(); const res2 = applyPackageRules({ ...config, - depName: 'bar', + packageName: 'bar', }); expect(res2.x).toBeDefined(); }); @@ -263,17 +189,16 @@ describe('util/package-rules/index', () => { const config: TestConfig = { packageRules: [ { - matchPackageNames: ['neutrino'], - matchPackagePatterns: ['^@neutrino\\/'], + matchPackageNames: ['neutrino', '/^@neutrino\\//'], x: 1, }, ], }; - const res1 = applyPackageRules({ ...config, depName: 'neutrino' }); + const res1 = applyPackageRules({ ...config, packageName: 'neutrino' }); expect(res1.x).toBeDefined(); const res2 = applyPackageRules({ ...config, - depName: '@neutrino/something', + packageName: '@neutrino/something', }); expect(res2.x).toBeDefined(); }); @@ -290,7 +215,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); @@ -308,12 +233,29 @@ describe('util/package-rules/index', () => { }; const dep = { depTypes: ['build', 'test'], - depName: 'a', + packageName: 'a', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('returns false if no depTypes', () => { + const config: TestConfig = { + packageRules: [ + { + matchDepTypes: ['test'], + matchPackageNames: ['a'], + x: 1, + }, + ], + }; + const input = { ...config, packageName: 'a' }; + delete input.depType; + delete input.depTypes; + const res = applyPackageRules(input); + expect(res).toEqual(input); + }); + it('filters managers with matching manager', () => { const config: TestConfig = { packageRules: [ @@ -327,7 +269,7 @@ describe('util/package-rules/index', () => { const dep = { depType: 'dependencies', manager: 'meteor', - depName: 'node', + packageName: 'node', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); @@ -348,7 +290,7 @@ describe('util/package-rules/index', () => { language: 'python', categories: ['python'], manager: 'pipenv', - depName: 'node', + packageName: 'node', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); @@ -368,7 +310,7 @@ describe('util/package-rules/index', () => { depType: 'dependencies', categories: ['javascript', 'node'], manager: 'meteor', - depName: 'node', + packageName: 'node', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); @@ -388,7 +330,7 @@ describe('util/package-rules/index', () => { depType: 'dependencies', categories: ['python'], manager: 'pipenv', - depName: 'node', + packageName: 'node', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); @@ -406,7 +348,7 @@ describe('util/package-rules/index', () => { const dep = { depType: 'dependencies', manager: 'pipenv', - depName: 'node', + packageName: 'node', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); @@ -538,7 +480,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, }; const res = applyPackageRules({ ...config, ...dep }); @@ -546,13 +488,13 @@ describe('util/package-rules/index', () => { expect(res.y).toBeUndefined(); }); - it('matches matchSourceUrlPrefixes', () => { + it('matches matchSourceUrls with glob', () => { const config: TestConfig = { packageRules: [ { - matchSourceUrlPrefixes: [ - 'https://github.com/foo/bar', - 'https://github.com/renovatebot/', + matchSourceUrls: [ + 'https://github.com/foo/bar**', + 'https://github.com/renovatebot/**', ], x: 1, }, @@ -560,7 +502,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/presets', }; @@ -568,13 +510,13 @@ describe('util/package-rules/index', () => { expect(res.x).toBe(1); }); - it('non-matches matchSourceUrlPrefixes', () => { + it('non-matches matchSourceUrls with globs', () => { const config: TestConfig = { packageRules: [ { - matchSourceUrlPrefixes: [ - 'https://github.com/foo/bar', - 'https://github.com/renovatebot/', + matchSourceUrls: [ + 'https://github.com/foo/bar**', + 'https://github.com/renovatebot/**', ], x: 1, }, @@ -582,7 +524,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/vuejs/vue', }; @@ -590,13 +532,13 @@ describe('util/package-rules/index', () => { expect(res.x).toBeUndefined(); }); - it('handles matchSourceUrlPrefixes when missing sourceUrl', () => { + it('handles matchSourceUrls when missing sourceUrl', () => { const config: TestConfig = { packageRules: [ { - matchSourceUrlPrefixes: [ - 'https://github.com/foo/bar', - 'https://github.com/renovatebot/', + matchSourceUrls: [ + 'https://github.com/foo/bar**', + 'https://github.com/renovatebot/**', ], x: 1, }, @@ -604,7 +546,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, }; const res = applyPackageRules({ ...config, ...dep }); @@ -625,7 +567,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/presets', }; @@ -647,7 +589,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/facebook/react-native', }; @@ -655,27 +597,6 @@ describe('util/package-rules/index', () => { expect(res.x).toBeUndefined(); }); - it('handles matchSourceUrls when missing sourceUrl', () => { - const config: TestConfig = { - packageRules: [ - { - matchSourceUrls: [ - 'https://github.com/foo/bar', - 'https://github.com/renovatebot/', - ], - x: 1, - }, - ], - }; - const dep = { - depType: 'dependencies', - depName: 'a', - updateType: 'patch' as UpdateType, - }; - const res = applyPackageRules({ ...config, ...dep }); - expect(res.x).toBeUndefined(); - }); - describe('matchConfidence', () => { const hostRule: HostRule = { hostType: 'merge-confidence', @@ -698,7 +619,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', mergeConfidenceLevel: 'high' as MergeConfidence, }; const res = applyPackageRules({ ...config, ...dep }); @@ -716,7 +637,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', mergeConfidenceLevel: 'low' as MergeConfidence, }; const res = applyPackageRules({ ...config, ...dep }); @@ -734,7 +655,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', mergeConfidenceLevel: undefined, }; const res = applyPackageRules({ ...config, ...dep }); @@ -779,7 +700,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); @@ -797,7 +718,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'devDependencies', - depName: 'a', + packageName: 'a', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); @@ -817,7 +738,7 @@ describe('util/package-rules/index', () => { const res1 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '^1.0.0', currentVersion: '1.0.3', }, @@ -826,7 +747,7 @@ describe('util/package-rules/index', () => { const res2 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '^1.0.0', }, }); @@ -834,7 +755,7 @@ describe('util/package-rules/index', () => { const res3 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', lockedVersion: '^1.0.0', }, }); @@ -855,7 +776,7 @@ describe('util/package-rules/index', () => { const res1 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '2.4.6', currentVersion: '2.4.6', }, @@ -877,7 +798,7 @@ describe('util/package-rules/index', () => { const res1 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '^2.0.0', }, }); @@ -885,7 +806,7 @@ describe('util/package-rules/index', () => { const res2 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '~2.0.0', }, }); @@ -905,7 +826,7 @@ describe('util/package-rules/index', () => { const res1 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '4.6.0', currentVersion: '4.6.0', }, @@ -926,7 +847,7 @@ describe('util/package-rules/index', () => { const res1 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '4.6.0', currentVersion: '4.6.0', }, @@ -934,7 +855,7 @@ describe('util/package-rules/index', () => { const res2 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '5.6.0', currentVersion: '5.6.0', }, @@ -956,7 +877,7 @@ describe('util/package-rules/index', () => { const res1 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '4.6.0', currentVersion: '4.6.0', }, @@ -964,7 +885,7 @@ describe('util/package-rules/index', () => { const res2 = applyPackageRules({ ...config, ...{ - depName: 'test', + packageName: 'test', currentValue: '5.6.0', currentVersion: '5.6.0', }, @@ -985,13 +906,13 @@ describe('util/package-rules/index', () => { }; const res1 = applyPackageRules({ ...config, - depName: 'test', + packageName: 'test', }); expect(res1.x).toBeUndefined(); config.packageFile = 'package.json'; const res2 = applyPackageRules({ ...config, - depName: 'test', + packageName: 'test', }); expect(res2.x).toBeDefined(); }); @@ -1023,19 +944,19 @@ describe('util/package-rules/index', () => { }; const res1 = applyPackageRules({ ...config, - depName: 'test', + packageName: 'test', }); expect(res1.x).toBeDefined(); config.packageFile = 'package.json'; const res2 = applyPackageRules({ ...config, - depName: 'test', + packageName: 'test', }); expect(res2.x).toBeUndefined(); config.packageFile = 'lib/a/package.json'; const res3 = applyPackageRules({ ...config, - depName: 'test', + packageName: 'test', }); expect(res3.x).toBeUndefined(); }); @@ -1051,15 +972,15 @@ describe('util/package-rules/index', () => { it('creates groupSlug if necessary', () => { const config: TestConfig = { - depName: 'foo', + packageName: 'foo', packageRules: [ { - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], groupName: 'A', groupSlug: 'a', }, { - matchPackagePatterns: ['*'], + matchPackageNames: ['*'], groupName: 'B', }, ], @@ -1068,13 +989,13 @@ describe('util/package-rules/index', () => { expect(res.groupSlug).toBe('b'); }); - it('matches matchSourceUrlPrefixes(case-insensitive)', () => { + it('matches matchSourceUrls with patterns (case-insensitive)', () => { const config: TestConfig = { packageRules: [ { - matchSourceUrlPrefixes: [ - 'https://github.com/foo/bar', - 'https://github.com/Renovatebot/', + matchSourceUrls: [ + 'https://github.com/foo/bar**', + 'https://github.com/Renovatebot/**', ], x: 1, }, @@ -1082,7 +1003,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/Presets', }; @@ -1104,7 +1025,7 @@ describe('util/package-rules/index', () => { }; const dep = { depType: 'dependencies', - depName: 'a', + packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/Renovate', }; @@ -1123,7 +1044,7 @@ describe('util/package-rules/index', () => { ], }; const dep = { - depName: 'abc', + packageName: 'abc', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); @@ -1140,7 +1061,7 @@ describe('util/package-rules/index', () => { ], }; const dep = { - depName: 'abc', + packageName: 'abc', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); @@ -1157,7 +1078,7 @@ describe('util/package-rules/index', () => { ], }; const dep = { - depName: 'abc', + packageName: 'abc', }; const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); @@ -1187,123 +1108,20 @@ describe('util/package-rules/index', () => { expect(res2.x).toBeUndefined(); }); - it('matches excludeDepNames(depName)', () => { + it('matches if there are no matchers', () => { const config: TestConfig = { packageRules: [ { - excludeDepNames: ['test1'], x: 1, }, ], }; - const res1 = applyPackageRules({ - ...config, - depName: 'test1', - }); - const res2 = applyPackageRules({ + const res = applyPackageRules({ ...config, depName: 'test2', }); - applyPackageRules(config); // coverage - - expect(res1.x).toBeUndefined(); - expect(res2.x).toBe(1); - }); - - it('matches matchDepPatterns(depName)', () => { - const config: TestConfig = { - packageRules: [ - { - matchDepPatterns: ['^test$'], - x: 1, - }, - ], - }; - const res1 = applyPackageRules({ - ...config, - depName: 'test', - }); - const res2 = applyPackageRules({ - ...config, - depName: 'test1', - }); - applyPackageRules(config); // coverage - - expect(res1.x).toBe(1); - expect(res2.x).toBeUndefined(); - }); - - it('matches excludeDepPatterns(depName)', () => { - const config: TestConfig = { - packageRules: [ - { - excludeDepPatterns: ['^test$'], - x: 1, - }, - ], - }; - - const res1 = applyPackageRules({ - ...config, - depName: 'test', - }); - const res2 = applyPackageRules({ - ...config, - depName: 'test1', - }); - applyPackageRules(config); // coverage - - expect(res1.x).toBeUndefined(); - expect(res2.x).toBe(1); - }); - - it('matches matchDepPrefixes(depName)', () => { - const config: TestConfig = { - packageRules: [ - { - matchDepPrefixes: ['abc'], - x: 1, - }, - ], - }; - - const res1 = applyPackageRules({ - ...config, - depName: 'abc1', - }); - const res2 = applyPackageRules({ - ...config, - depName: 'def1', - }); - applyPackageRules(config); // coverage - - expect(res1.x).toBe(1); - expect(res2.x).toBeUndefined(); - }); - - it('matches excludeDepPrefixes(depName)', () => { - const config: TestConfig = { - packageRules: [ - { - excludeDepPrefixes: ['abc'], - x: 1, - }, - ], - }; - - const res1 = applyPackageRules({ - ...config, - depName: 'abc1', - }); - const res2 = applyPackageRules({ - ...config, - depName: 'def1', - }); - applyPackageRules(config); // coverage - - expect(res1.x).toBeUndefined(); - expect(res2.x).toBe(1); + expect(res.x).toBe(1); }); }); diff --git a/lib/util/package-rules/index.ts b/lib/util/package-rules/index.ts index f625ea5b24462f..dd6c54ba9194cf 100644 --- a/lib/util/package-rules/index.ts +++ b/lib/util/package-rules/index.ts @@ -4,60 +4,25 @@ import { mergeChildConfig } from '../../config'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; import { logger } from '../../logger'; import matchers from './matchers'; -import { matcherOR } from './utils'; function matchesRule( inputConfig: PackageRuleInputConfig, packageRule: PackageRule, ): boolean { - let positiveMatch = true; - let matchApplied = false; - // matches - for (const groupMatchers of matchers) { - const isMatch = matcherOR( - 'matches', - groupMatchers, - inputConfig, - packageRule, - ); + for (const matcher of matchers) { + const isMatch = matcher.matches(inputConfig, packageRule); // no rules are defined if (is.nullOrUndefined(isMatch)) { continue; } - matchApplied = true; - if (!is.truthy(isMatch)) { return false; } } - // not a single match rule is defined --> assume to match everything - if (!matchApplied) { - positiveMatch = true; - } - - // excludes - for (const groupExcludes of matchers) { - const isExclude = matcherOR( - 'excludes', - groupExcludes, - inputConfig, - packageRule, - ); - - // no rules are defined - if (is.nullOrUndefined(isExclude)) { - continue; - } - - if (isExclude) { - return false; - } - } - - return positiveMatch; + return true; } export function applyPackageRules( diff --git a/lib/util/package-rules/managers.ts b/lib/util/package-rules/managers.ts index 98c28ce198eeaf..487bcf3b0b0bcd 100644 --- a/lib/util/package-rules/managers.ts +++ b/lib/util/package-rules/managers.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; import { isCustomManager } from '../../modules/manager/custom'; +import { matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class ManagersMatcher extends Matcher { @@ -15,8 +16,8 @@ export class ManagersMatcher extends Matcher { return false; } if (isCustomManager(manager)) { - return matchManagers.includes(`custom.${manager}`); + return matchRegexOrGlobList(`custom.${manager}`, matchManagers); } - return matchManagers.includes(manager); + return matchRegexOrGlobList(manager, matchManagers); } } diff --git a/lib/util/package-rules/matchers.ts b/lib/util/package-rules/matchers.ts index 61f68973c9ae54..9ad89afe0a92aa 100644 --- a/lib/util/package-rules/matchers.ts +++ b/lib/util/package-rules/matchers.ts @@ -5,23 +5,18 @@ import { CurrentValueMatcher } from './current-value'; import { CurrentVersionMatcher } from './current-version'; import { DatasourcesMatcher } from './datasources'; import { DepNameMatcher } from './dep-names'; -import { DepPatternsMatcher } from './dep-patterns'; -import { DepPrefixesMatcher } from './dep-prefixes'; import { DepTypesMatcher } from './dep-types'; import { FileNamesMatcher } from './files'; import { ManagersMatcher } from './managers'; import { MergeConfidenceMatcher } from './merge-confidence'; import { NewValueMatcher } from './new-value'; import { PackageNameMatcher } from './package-names'; -import { PackagePatternsMatcher } from './package-patterns'; -import { PackagePrefixesMatcher } from './package-prefixes'; import { RepositoriesMatcher } from './repositories'; -import { SourceUrlPrefixesMatcher } from './sourceurl-prefixes'; import { SourceUrlsMatcher } from './sourceurls'; import type { MatcherApi } from './types'; import { UpdateTypesMatcher } from './update-types'; -const matchers: MatcherApi[][] = []; +const matchers: MatcherApi[] = []; export default matchers; // Each matcher under the same index will use a logical OR, if multiple matchers are applied AND will be used @@ -29,25 +24,19 @@ export default matchers; // applyPackageRules evaluates matchers in the order of insertion and returns early on failure. // Therefore, when multiple matchers are set in a single packageRule, some may not be checked. // Since matchConfidence matcher can abort the run due to unauthenticated use, it should be evaluated first. -matchers.push([new MergeConfidenceMatcher()]); -matchers.push([new RepositoriesMatcher()]); -matchers.push([new BaseBranchesMatcher()]); -matchers.push([new CategoriesMatcher()]); -matchers.push([new ManagersMatcher()]); -matchers.push([new FileNamesMatcher()]); -matchers.push([new DatasourcesMatcher()]); -matchers.push([ - new DepNameMatcher(), - new DepPatternsMatcher(), - new DepPrefixesMatcher(), - new PackageNameMatcher(), - new PackagePatternsMatcher(), - new PackagePrefixesMatcher(), -]); -matchers.push([new DepTypesMatcher()]); -matchers.push([new CurrentValueMatcher()]); -matchers.push([new CurrentVersionMatcher()]); -matchers.push([new UpdateTypesMatcher()]); -matchers.push([new SourceUrlsMatcher(), new SourceUrlPrefixesMatcher()]); -matchers.push([new NewValueMatcher()]); -matchers.push([new CurrentAgeMatcher()]); +matchers.push(new MergeConfidenceMatcher()); +matchers.push(new RepositoriesMatcher()); +matchers.push(new BaseBranchesMatcher()); +matchers.push(new CategoriesMatcher()); +matchers.push(new ManagersMatcher()); +matchers.push(new FileNamesMatcher()); +matchers.push(new DatasourcesMatcher()); +matchers.push(new PackageNameMatcher()); +matchers.push(new DepNameMatcher()); +matchers.push(new DepTypesMatcher()); +matchers.push(new CurrentValueMatcher()); +matchers.push(new CurrentVersionMatcher()); +matchers.push(new UpdateTypesMatcher()); +matchers.push(new SourceUrlsMatcher()); +matchers.push(new NewValueMatcher()); +matchers.push(new CurrentAgeMatcher()); diff --git a/lib/util/package-rules/matchers.ts.orig b/lib/util/package-rules/matchers.ts.orig new file mode 100644 index 00000000000000..cab1058e1d2b14 --- /dev/null +++ b/lib/util/package-rules/matchers.ts.orig @@ -0,0 +1,67 @@ +import { BaseBranchesMatcher } from './base-branches'; +import { CategoriesMatcher } from './categories'; +import { CurrentAgeMatcher } from './current-age'; +import { CurrentValueMatcher } from './current-value'; +import { CurrentVersionMatcher } from './current-version'; +import { DatasourcesMatcher } from './datasources'; +import { DepNameMatcher } from './dep-names'; +import { DepTypesMatcher } from './dep-types'; +import { FileNamesMatcher } from './files'; +import { ManagersMatcher } from './managers'; +import { MergeConfidenceMatcher } from './merge-confidence'; +import { NewValueMatcher } from './new-value'; +import { PackageNameMatcher } from './package-names'; +import { RepositoriesMatcher } from './repositories'; +import { SourceUrlsMatcher } from './sourceurls'; +import type { MatcherApi } from './types'; +import { UpdateTypesMatcher } from './update-types'; + +const matchers: MatcherApi[] = []; +export default matchers; + +// Each matcher under the same index will use a logical OR, if multiple matchers are applied AND will be used + +// applyPackageRules evaluates matchers in the order of insertion and returns early on failure. +// Therefore, when multiple matchers are set in a single packageRule, some may not be checked. +// Since matchConfidence matcher can abort the run due to unauthenticated use, it should be evaluated first. +<<<<<<< HEAD +matchers.push([new MergeConfidenceMatcher()]); +matchers.push([new RepositoriesMatcher()]); +matchers.push([new BaseBranchesMatcher()]); +matchers.push([new CategoriesMatcher()]); +matchers.push([new ManagersMatcher()]); +matchers.push([new FileNamesMatcher()]); +matchers.push([new DatasourcesMatcher()]); +matchers.push([ + new DepNameMatcher(), + new DepPatternsMatcher(), + new DepPrefixesMatcher(), + new PackageNameMatcher(), + new PackagePatternsMatcher(), + new PackagePrefixesMatcher(), +]); +matchers.push([new DepTypesMatcher()]); +matchers.push([new CurrentValueMatcher()]); +matchers.push([new CurrentVersionMatcher()]); +matchers.push([new UpdateTypesMatcher()]); +matchers.push([new SourceUrlsMatcher(), new SourceUrlPrefixesMatcher()]); +matchers.push([new NewValueMatcher()]); +matchers.push([new CurrentAgeMatcher()]); +======= +matchers.push(new MergeConfidenceMatcher()); +matchers.push(new DepNameMatcher()); +matchers.push(new PackageNameMatcher()); +matchers.push(new FileNamesMatcher()); +matchers.push(new DepTypesMatcher()); +matchers.push(new BaseBranchesMatcher()); +matchers.push(new ManagersMatcher()); +matchers.push(new DatasourcesMatcher()); +matchers.push(new UpdateTypesMatcher()); +matchers.push(new SourceUrlsMatcher()); +matchers.push(new CurrentValueMatcher()); +matchers.push(new NewValueMatcher()); +matchers.push(new CurrentVersionMatcher()); +matchers.push(new RepositoriesMatcher()); +matchers.push(new CategoriesMatcher()); +matchers.push(new CurrentAgeMatcher()); +>>>>>>> b66597a89 (feat(packageRules): migrate matchers and excludes (#28602)) diff --git a/lib/util/package-rules/package-names.spec.ts b/lib/util/package-rules/package-names.spec.ts index 522f0ea0c3cc2c..47fd4fcdfa20fb 100644 --- a/lib/util/package-rules/package-names.spec.ts +++ b/lib/util/package-rules/package-names.spec.ts @@ -1,14 +1,13 @@ -import { logger } from '../../../test/util'; import { PackageNameMatcher } from './package-names'; describe('util/package-rules/package-names', () => { const packageNameMatcher = new PackageNameMatcher(); describe('match', () => { - it('should return false if packageFile is not defined', () => { + it('should return false if packageName is not defined', () => { const result = packageNameMatcher.matches( { - depName: undefined, + packageName: undefined, }, { matchPackageNames: ['@opentelemetry/http'], @@ -33,7 +32,6 @@ describe('util/package-rules/package-names', () => { it('should matchPackageName', () => { const result = packageNameMatcher.matches( { - depName: 'abc', packageName: 'def', }, { @@ -43,72 +41,16 @@ describe('util/package-rules/package-names', () => { expect(result).toBeTrue(); }); - it('should fall back to matching depName', () => { + it('should match pattern', () => { const result = packageNameMatcher.matches( { - depName: 'abc', - packageName: 'def', - }, - { - matchPackageNames: ['ghi', 'abc'], - }, - ); - expect(result).toBeTrue(); - expect(logger.logger.once.warn).toHaveBeenCalled(); - }); - }); - - describe('exclude', () => { - it('should return false if packageName is not defined', () => { - const result = packageNameMatcher.excludes( - { - depName: undefined, - }, - { - excludePackageNames: ['@opentelemetry/http'], - }, - ); - expect(result).toBeFalse(); - }); - - it('should return false if not matching', () => { - const result = packageNameMatcher.excludes( - { - depName: 'abc', - packageName: 'def', - }, - { - excludePackageNames: ['ghi'], - }, - ); - expect(result).toBeFalse(); - }); - - it('should excludePackageName', () => { - const result = packageNameMatcher.excludes( - { - depName: 'abc', - packageName: 'def', - }, - { - excludePackageNames: ['def', 'ghi'], - }, - ); - expect(result).toBeTrue(); - }); - - it('should fall back to depName excludePackageName', () => { - const result = packageNameMatcher.excludes( - { - depName: 'abc', - packageName: 'def', + packageName: 'b', }, { - excludePackageNames: ['abc', 'ghi'], + matchPackageNames: ['/b/'], }, ); expect(result).toBeTrue(); - expect(logger.logger.once.warn).toHaveBeenCalled(); }); }); }); diff --git a/lib/util/package-rules/package-names.ts b/lib/util/package-rules/package-names.ts index ee8cc6befb14c7..9586fb5caa5fbe 100644 --- a/lib/util/package-rules/package-names.ts +++ b/lib/util/package-rules/package-names.ts @@ -1,60 +1,20 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { logger } from '../../logger'; +import { matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class PackageNameMatcher extends Matcher { override matches( - { depName, packageName }: PackageRuleInputConfig, + { packageName }: PackageRuleInputConfig, packageRule: PackageRule, ): boolean | null { const { matchPackageNames } = packageRule; if (is.undefined(matchPackageNames)) { return null; } - if (is.undefined(depName)) { + if (!packageName) { return false; } - - if (is.string(packageName) && matchPackageNames.includes(packageName)) { - return true; - } - - if (matchPackageNames.includes(depName)) { - logger.once.warn( - { packageRule, packageName, depName }, - 'Use matchDepNames instead of matchPackageNames', - ); - return true; - } - - return false; - } - - override excludes( - { depName, packageName }: PackageRuleInputConfig, - packageRule: PackageRule, - ): boolean | null { - const { excludePackageNames } = packageRule; - if (is.undefined(excludePackageNames)) { - return null; - } - if (is.undefined(depName)) { - return false; - } - - if (is.string(packageName) && excludePackageNames.includes(packageName)) { - return true; - } - - if (excludePackageNames.includes(depName)) { - logger.once.warn( - { packageRule, packageName, depName }, - 'Use excludeDepNames instead of excludePackageNames', - ); - return true; - } - - return false; + return matchRegexOrGlobList(packageName, matchPackageNames); } } diff --git a/lib/util/package-rules/package-patterns.spec.ts b/lib/util/package-rules/package-patterns.spec.ts deleted file mode 100644 index e6b1665510be3a..00000000000000 --- a/lib/util/package-rules/package-patterns.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { PackagePatternsMatcher } from './package-patterns'; - -describe('util/package-rules/package-patterns', () => { - const packagePatternsMatcher = new PackagePatternsMatcher(); - - describe('match', () => { - it('should return false if depName is not defined', () => { - const result = packagePatternsMatcher.matches( - { - depName: undefined, - }, - { - matchPackagePatterns: ['@opentelemetry/http'], - }, - ); - expect(result).toBeFalse(); - }); - - it('should match packageName', () => { - const result = packagePatternsMatcher.matches( - { - depName: 'abc', - packageName: 'def', - }, - { - matchPackagePatterns: ['def'], - }, - ); - expect(result).toBeTrue(); - }); - - it('should fall back to matching depName', () => { - const result = packagePatternsMatcher.matches( - { - depName: 'abc', - packageName: 'def', - }, - { - matchPackagePatterns: ['abc'], - }, - ); - expect(result).toBeTrue(); - }); - }); - - describe('exclude', () => { - it('should exclude packageName', () => { - const result = packagePatternsMatcher.excludes( - { - depName: 'abc', - packageName: 'def', - }, - { - excludePackagePatterns: ['def'], - }, - ); - expect(result).toBeTrue(); - }); - }); -}); diff --git a/lib/util/package-rules/package-patterns.ts b/lib/util/package-rules/package-patterns.ts deleted file mode 100644 index 9fc28c7509f821..00000000000000 --- a/lib/util/package-rules/package-patterns.ts +++ /dev/null @@ -1,91 +0,0 @@ -import is from '@sindresorhus/is'; -import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { logger } from '../../logger'; -import { regEx } from '../regex'; -import { Matcher } from './base'; -import { massagePattern } from './utils'; - -function matchPatternsAgainstName( - matchPackagePatterns: string[], - name: string, -): boolean { - let isMatch = false; - for (const packagePattern of matchPackagePatterns) { - if (isPackagePatternMatch(packagePattern, name)) { - isMatch = true; - } - } - return isMatch; -} - -export class PackagePatternsMatcher extends Matcher { - override matches( - { depName, packageName }: PackageRuleInputConfig, - packageRule: PackageRule, - ): boolean | null { - const { matchPackagePatterns } = packageRule; - if (is.undefined(matchPackagePatterns)) { - return null; - } - - if (is.undefined(depName)) { - return false; - } - - if ( - is.string(packageName) && - matchPatternsAgainstName(matchPackagePatterns, packageName) - ) { - return true; - } - if (matchPatternsAgainstName(matchPackagePatterns, depName)) { - logger.once.warn( - { packageRule, packageName, depName }, - 'Use matchDepPatterns instead of matchPackagePatterns', - ); - return true; - } - - return false; - } - - override excludes( - { depName, packageName }: PackageRuleInputConfig, - packageRule: PackageRule, - ): boolean | null { - const { excludePackagePatterns } = packageRule; - // ignore lockFileMaintenance for backwards compatibility - if (is.undefined(excludePackagePatterns)) { - return null; - } - if (is.undefined(depName)) { - return false; - } - - if ( - is.string(packageName) && - matchPatternsAgainstName(excludePackagePatterns, packageName) - ) { - return true; - } - - if (matchPatternsAgainstName(excludePackagePatterns, depName)) { - logger.once.warn( - { packageRule, packageName, depName }, - 'Use excludeDepPatterns instead of excludePackagePatterns', - ); - return true; - } - - return false; - } -} - -function isPackagePatternMatch(pckPattern: string, pck: string): boolean { - const re = regEx(massagePattern(pckPattern)); - if (re.test(pck)) { - logger.trace(`${pck} matches against ${String(re)}`); - return true; - } - return false; -} diff --git a/lib/util/package-rules/package-prefixes.spec.ts b/lib/util/package-rules/package-prefixes.spec.ts deleted file mode 100644 index b68aa0a65cfdcc..00000000000000 --- a/lib/util/package-rules/package-prefixes.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { PackagePrefixesMatcher } from './package-prefixes'; - -describe('util/package-rules/package-prefixes', () => { - const packagePrefixesMatcher = new PackagePrefixesMatcher(); - - describe('match', () => { - it('should return false if depName is not defined', () => { - const result = packagePrefixesMatcher.matches( - { - depName: undefined, - }, - { - matchPackagePrefixes: ['@opentelemetry'], - }, - ); - expect(result).toBeFalse(); - }); - - it('should return true if packageName matched', () => { - const result = packagePrefixesMatcher.matches( - { - depName: 'abc1', - packageName: 'def1', - }, - { - matchPackagePrefixes: ['def'], - }, - ); - expect(result).toBeTrue(); - }); - - it('should return true but warn if depName matched', () => { - const result = packagePrefixesMatcher.matches( - { - depName: 'abc1', - packageName: 'def1', - }, - { - matchPackagePrefixes: ['abc'], - }, - ); - expect(result).toBeTrue(); - }); - }); - - describe('exclude', () => { - it('should return false if depName is not defined', () => { - const result = packagePrefixesMatcher.excludes( - { - depName: undefined, - }, - { - excludePackagePrefixes: ['@opentelemetry'], - }, - ); - expect(result).toBeFalse(); - }); - - it('should return true if packageName matched', () => { - const result = packagePrefixesMatcher.excludes( - { - depName: 'abc1', - packageName: 'def1', - }, - { - excludePackagePrefixes: ['def'], - }, - ); - expect(result).toBeTrue(); - }); - }); -}); diff --git a/lib/util/package-rules/package-prefixes.ts b/lib/util/package-rules/package-prefixes.ts deleted file mode 100644 index c52f4e14e40f3e..00000000000000 --- a/lib/util/package-rules/package-prefixes.ts +++ /dev/null @@ -1,65 +0,0 @@ -import is from '@sindresorhus/is'; -import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { logger } from '../../logger'; -import { Matcher } from './base'; - -export class PackagePrefixesMatcher extends Matcher { - override matches( - { depName, packageName }: PackageRuleInputConfig, - packageRule: PackageRule, - ): boolean | null { - const { matchPackagePrefixes } = packageRule; - if (is.undefined(matchPackagePrefixes)) { - return null; - } - - if (is.undefined(depName)) { - return false; - } - - if ( - is.string(packageName) && - matchPackagePrefixes.some((prefix) => packageName.startsWith(prefix)) - ) { - return true; - } - if (matchPackagePrefixes.some((prefix) => depName.startsWith(prefix))) { - logger.once.warn( - { packageRule, packageName, depName }, - 'Use matchDepPrefixes instead of matchPackagePrefixes', - ); - return true; - } - - return false; - } - - override excludes( - { depName, packageName }: PackageRuleInputConfig, - packageRule: PackageRule, - ): boolean | null { - const { excludePackagePrefixes } = packageRule; - if (is.undefined(excludePackagePrefixes)) { - return null; - } - if (is.undefined(depName)) { - return false; - } - - if ( - is.string(packageName) && - excludePackagePrefixes.some((prefix) => packageName.startsWith(prefix)) - ) { - return true; - } - if (excludePackagePrefixes.some((prefix) => depName.startsWith(prefix))) { - logger.once.warn( - { packageRule, packageName, depName }, - 'Use excludeDepPrefixes instead of excludePackagePrefixes', - ); - return true; - } - - return false; - } -} diff --git a/lib/util/package-rules/repositories.spec.ts b/lib/util/package-rules/repositories.spec.ts index 369927de30b157..73b93cf06e1bbb 100644 --- a/lib/util/package-rules/repositories.spec.ts +++ b/lib/util/package-rules/repositories.spec.ts @@ -102,100 +102,88 @@ describe('util/package-rules/repositories', () => { }); describe('excludes', () => { - it('should return null if exclude repositories is not defined', () => { - const result = repositoryMatcher.excludes( - { - repository: 'org/repo', - }, - { - excludeRepositories: undefined, - }, - ); - expect(result).toBeNull(); - }); - it('should return false if exclude repository is not defined', () => { - const result = repositoryMatcher.excludes( + const result = repositoryMatcher.matches( { repository: undefined, }, { - excludeRepositories: ['org/repo'], + matchRepositories: ['!org/repo'], }, ); expect(result).toBeFalse(); }); - it('should return true if exclude repository matches regex pattern', () => { - const result = repositoryMatcher.excludes( + it('should return false if exclude repository matches regex pattern', () => { + const result = repositoryMatcher.matches( { repository: 'org/repo', }, { - excludeRepositories: ['/^org/repo$/'], + matchRepositories: ['!/^org/repo$/'], }, ); - expect(result).toBeTrue(); + expect(result).toBeFalse(); }); - it('should return false if exclude repository has invalid regex pattern', () => { - const result = repositoryMatcher.excludes( + it('should return true if exclude repository has invalid regex pattern', () => { + const result = repositoryMatcher.matches( { repository: 'org/repo', }, { - excludeRepositories: ['/[/'], + matchRepositories: ['!/[/'], }, ); - expect(result).toBeFalse(); + expect(result).toBeTrue(); }); - it('should return false if exclude repository does not match regex pattern', () => { - const result = repositoryMatcher.excludes( + it('should return true if exclude repository does not match regex pattern', () => { + const result = repositoryMatcher.matches( { repository: 'org/repo', }, { - excludeRepositories: ['/^org/other-repo$/'], + matchRepositories: ['!/^org/other-repo$/'], }, ); - expect(result).toBeFalse(); + expect(result).toBeTrue(); }); - it('should return true if exclude repository matches minimatch pattern', () => { - const result = repositoryMatcher.excludes( + it('should return false if exclude repository matches minimatch pattern', () => { + const result = repositoryMatcher.matches( { repository: 'org/repo', }, { - excludeRepositories: ['org/**'], + matchRepositories: ['!org/**'], }, ); - expect(result).toBeTrue(); + expect(result).toBeFalse(); }); - it('should return false if exclude repository does not match minimatch pattern', () => { - const result = repositoryMatcher.excludes( + it('should return true if exclude repository does not match minimatch pattern', () => { + const result = repositoryMatcher.matches( { repository: 'org/repo', }, { - excludeRepositories: ['other-org/**'], + matchRepositories: ['!other-org/**'], }, ); - expect(result).toBeFalse(); + expect(result).toBeTrue(); }); - it('should return true if exclude repository matches at least one pattern', () => { - const result = repositoryMatcher.excludes( + it('should return false if exclude repository matches at least one pattern', () => { + const result = repositoryMatcher.matches( { repository: 'org/repo-archived', }, { - excludeRepositories: ['/^org/repo$/', '**/*-archived'], + matchRepositories: ['!/^org/repo$/', '!**/*-archived'], }, ); - expect(result).toBeTrue(); + expect(result).toBeFalse(); }); }); }); diff --git a/lib/util/package-rules/repositories.ts b/lib/util/package-rules/repositories.ts index bc3c45ac8fb43c..9e47c0c475f95d 100644 --- a/lib/util/package-rules/repositories.ts +++ b/lib/util/package-rules/repositories.ts @@ -18,19 +18,4 @@ export class RepositoriesMatcher extends Matcher { return matchRegexOrGlobList(repository, matchRepositories); } - - override excludes( - { repository }: PackageRuleInputConfig, - { excludeRepositories }: PackageRule, - ): boolean | null { - if (is.undefined(excludeRepositories)) { - return null; - } - - if (is.undefined(repository)) { - return false; - } - - return matchRegexOrGlobList(repository, excludeRepositories); - } } diff --git a/lib/util/package-rules/sourceurl-prefixes.ts b/lib/util/package-rules/sourceurl-prefixes.ts deleted file mode 100644 index 13d6d4801cb9d7..00000000000000 --- a/lib/util/package-rules/sourceurl-prefixes.ts +++ /dev/null @@ -1,22 +0,0 @@ -import is from '@sindresorhus/is'; -import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import { Matcher } from './base'; - -export class SourceUrlPrefixesMatcher extends Matcher { - override matches( - { sourceUrl }: PackageRuleInputConfig, - { matchSourceUrlPrefixes }: PackageRule, - ): boolean | null { - if (is.undefined(matchSourceUrlPrefixes)) { - return null; - } - if (is.undefined(sourceUrl)) { - return false; - } - const upperCaseSourceUrl = sourceUrl?.toUpperCase(); - - return matchSourceUrlPrefixes.some((prefix) => - upperCaseSourceUrl?.startsWith(prefix.toUpperCase()), - ); - } -} diff --git a/lib/util/package-rules/sourceurls.ts b/lib/util/package-rules/sourceurls.ts index 0f1e311435490d..d1d9910a0f7911 100644 --- a/lib/util/package-rules/sourceurls.ts +++ b/lib/util/package-rules/sourceurls.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; +import { matchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class SourceUrlsMatcher extends Matcher { @@ -10,13 +11,10 @@ export class SourceUrlsMatcher extends Matcher { if (is.undefined(matchSourceUrls)) { return null; } - if (is.undefined(sourceUrl)) { + if (!sourceUrl) { return false; } - const upperCaseSourceUrl = sourceUrl?.toUpperCase(); - return matchSourceUrls.some( - (url) => upperCaseSourceUrl === url.toUpperCase(), - ); + return matchRegexOrGlobList(sourceUrl, matchSourceUrls); } } diff --git a/lib/util/package-rules/types.ts b/lib/util/package-rules/types.ts index 0307f2e82d6b36..938232b86aa6d4 100644 --- a/lib/util/package-rules/types.ts +++ b/lib/util/package-rules/types.ts @@ -7,8 +7,4 @@ export interface MatcherApi { inputConfig: PackageRuleInputConfig, packageRule: PackageRule, ): boolean | null; - excludes( - inputConfig: PackageRuleInputConfig, - packageRule: PackageRule, - ): boolean | null; } diff --git a/lib/util/package-rules/update-types.ts b/lib/util/package-rules/update-types.ts index 85c5f3f750ea07..c54b26588ce9ee 100644 --- a/lib/util/package-rules/update-types.ts +++ b/lib/util/package-rules/update-types.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; +import { anyMatchRegexOrGlobList } from '../string-match'; import { Matcher } from './base'; export class UpdateTypesMatcher extends Matcher { @@ -10,9 +11,13 @@ export class UpdateTypesMatcher extends Matcher { if (is.undefined(matchUpdateTypes)) { return null; } - return ( - (is.truthy(updateType) && matchUpdateTypes.includes(updateType)) || - (is.truthy(isBump) && matchUpdateTypes.includes('bump')) - ); + if (!updateType) { + return false; + } + const toMatch = [updateType]; + if (isBump) { + toMatch.push('bump'); + } + return anyMatchRegexOrGlobList(toMatch, matchUpdateTypes); } } diff --git a/lib/util/package-rules/utils.ts b/lib/util/package-rules/utils.ts deleted file mode 100644 index 0a8cb830c61276..00000000000000 --- a/lib/util/package-rules/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import is from '@sindresorhus/is'; -import type { PackageRule, PackageRuleInputConfig } from '../../config/types'; -import type { MatchType, MatcherApi } from './types'; - -export function matcherOR( - matchType: MatchType, - groupMatchers: MatcherApi[], - inputConfig: PackageRuleInputConfig, - packageRule: PackageRule, -): boolean | null { - let matchApplied = false; - for (const matcher of groupMatchers) { - let isMatch; - switch (matchType) { - case 'excludes': - isMatch = matcher.excludes(inputConfig, packageRule); - break; - case 'matches': - isMatch = matcher.matches(inputConfig, packageRule); - break; - } - - // no rules are defined - if (is.nullOrUndefined(isMatch)) { - continue; - } - - matchApplied = true; - - if (is.truthy(isMatch)) { - return true; - } - } - return matchApplied ? false : null; -} - -export function massagePattern(pattern: string): string { - return pattern === '^*$' || pattern === '*' ? '.*' : pattern; -} diff --git a/lib/util/string-match.spec.ts b/lib/util/string-match.spec.ts index 0321d4ce5720d6..626da73e184ee1 100644 --- a/lib/util/string-match.spec.ts +++ b/lib/util/string-match.spec.ts @@ -1,4 +1,5 @@ import { + anyMatchRegexOrGlobList, getRegexPredicate, matchRegexOrGlob, matchRegexOrGlobList, @@ -14,6 +15,10 @@ describe('util/string-match', () => { expect(matchRegexOrGlobList('test', ['/test2/'])).toBeFalse(); }); + it('returns true if star', () => { + expect(matchRegexOrGlobList('&&&', ['*'])).toBeTrue(); + }); + it('returns true if any match', () => { expect(matchRegexOrGlobList('test', ['test', '/test2/'])).toBeTrue(); }); @@ -55,6 +60,28 @@ describe('util/string-match', () => { }); }); + describe('anyMatchRegexOrGlobList()', () => { + it('returns false if empty patterns', () => { + expect(anyMatchRegexOrGlobList(['test'], [])).toBeFalse(); + }); + + it('returns false if empty inputs', () => { + expect(anyMatchRegexOrGlobList([], ['/test2/'])).toBeFalse(); + }); + + it('returns true if both empty', () => { + expect(anyMatchRegexOrGlobList([], [])).toBeFalse(); + }); + + it('returns true if any match with positive', () => { + expect(anyMatchRegexOrGlobList(['a', 'b'], ['b'])).toBeTrue(); + }); + + it('returns true if any match with negative', () => { + expect(anyMatchRegexOrGlobList(['a', 'b'], ['!b'])).toBeTrue(); + }); + }); + describe('getRegexPredicate()', () => { it('allows valid regex pattern', () => { expect(getRegexPredicate('/hello/')).not.toBeNull(); diff --git a/lib/util/string-match.ts b/lib/util/string-match.ts index 241debfbd17d0d..bb64c327515c86 100644 --- a/lib/util/string-match.ts +++ b/lib/util/string-match.ts @@ -19,6 +19,9 @@ export function getRegexOrGlobPredicate(pattern: string): StringMatchPredicate { } export function matchRegexOrGlob(input: string, pattern: string): boolean { + if (pattern === '*') { + return true; + } const predicate = getRegexOrGlobPredicate(pattern); return predicate(input); } @@ -56,6 +59,13 @@ export function matchRegexOrGlobList( return true; } +export function anyMatchRegexOrGlobList( + inputs: string[], + patterns: string[], +): boolean { + return inputs.some((input) => matchRegexOrGlobList(input, patterns)); +} + export const UUIDRegex = regEx( /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, ); diff --git a/lib/workers/global/config/parse/env.spec.ts b/lib/workers/global/config/parse/env.spec.ts index 4efcc8cc1d526c..90e0210c2f1cf8 100644 --- a/lib/workers/global/config/parse/env.spec.ts +++ b/lib/workers/global/config/parse/env.spec.ts @@ -96,6 +96,29 @@ describe('workers/global/config/parse/env', () => { expect(await env.getConfig(envArg)).toMatchObject(config); }); + it('converts legacy experimental env vars to experimental flags', async () => { + const envArg = { + RENOVATE_X_DISABLE_DOCKER_HUB_TAGS: 'true', + RENOVATE_X_EXEC_GPID_HANDLE: 'true', + RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK: 'true', + RENOVATE_X_NUGET_DOWNLOAD_NUPKGS: 'true', + RENOVATE_X_REPO_CACHE_FORCE_LOCAL: 'true', + RENOVATE_X_YARN_PROXY: 'true', + RENOVATE_X_USE_OPENPGP: 'true', + }; + expect(await env.getConfig(envArg)).toMatchObject({ + experimentalFlags: [ + 'disableDockerHubTags', + 'execGpidHandle', + 'noMavenPomCheck', + 'nugetDownloadNupkgs', + 'repoCacheForceLocal', + 'yarnProxy', + 'useOpenpgp', + ], + }); + }); + it('skips misconfigured arrays', async () => { const envName = 'RENOVATE_HOST_RULES'; const val = JSON.stringify('foobar'); @@ -395,5 +418,21 @@ describe('workers/global/config/parse/env', () => { const config = await env.getConfig(envParam); expect(config.requireConfig).toBe('optional'); }); + + it('platformCommit boolean true', async () => { + const envParam: NodeJS.ProcessEnv = { + RENOVATE_PLATFORM_COMMIT: 'true', + }; + const config = await env.getConfig(envParam); + expect(config.platformCommit).toBe('enabled'); + }); + + it('platformCommit boolean false', async () => { + const envParam: NodeJS.ProcessEnv = { + RENOVATE_PLATFORM_COMMIT: 'false', + }; + const config = await env.getConfig(envParam); + expect(config.platformCommit).toBe('disabled'); + }); }); }); diff --git a/lib/workers/global/config/parse/env.ts b/lib/workers/global/config/parse/env.ts index b49cee0cbaeb89..60ed5f33a4572d 100644 --- a/lib/workers/global/config/parse/env.ts +++ b/lib/workers/global/config/parse/env.ts @@ -85,6 +85,28 @@ function massageEnvKeyValues(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { return result; } +// list of legacy experimental env vars which have been converted to experimental flags +const legacyExperimentalEnvVars = { + RENOVATE_X_DISABLE_DOCKER_HUB_TAGS: 'disableDockerHubTags', + RENOVATE_X_EXEC_GPID_HANDLE: 'execGpidHandle', + RENOVATE_EXPERIMENTAL_NO_MAVEN_POM_CHECK: 'noMavenPomCheck', + RENOVATE_X_NUGET_DOWNLOAD_NUPKGS: 'nugetDownloadNupkgs', + RENOVATE_X_REPO_CACHE_FORCE_LOCAL: 'repoCacheForceLocal', + RENOVATE_X_YARN_PROXY: 'yarnProxy', + RENOVATE_X_USE_OPENPGP: 'useOpenpgp', +}; + +function convertLegacyEnvVarsToFlags(env: NodeJS.ProcessEnv): string[] { + const res = []; + for (const [key, flag] of Object.entries(legacyExperimentalEnvVars)) { + if (env[key]) { + res.push(flag); + } + } + + return res; +} + const convertedExperimentalEnvVars = [ 'RENOVATE_X_AUTODISCOVER_REPO_SORT', 'RENOVATE_X_AUTODISCOVER_REPO_ORDER', @@ -192,6 +214,19 @@ export async function getConfig( config[option.name] = 'optional'; } } + if (option.name === 'platformCommit') { + if ((config[option.name] as string) === 'true') { + logger.warn( + 'env config platformCommit property has been changed to enabled', + ); + config[option.name] = 'enabled'; + } else if ((config[option.name] as string) === 'false') { + logger.warn( + 'env config platformCommit property has been changed to disabled', + ); + config[option.name] = 'disabled'; + } + } } } } @@ -206,6 +241,11 @@ export async function getConfig( }); } + const experimentalFlags = convertLegacyEnvVarsToFlags(env); + if (experimentalFlags.length) { + config.experimentalFlags = experimentalFlags; + } + // These env vars are deprecated and deleted to make sure they're not used const unsupportedEnv = [ 'BITBUCKET_TOKEN', diff --git a/lib/workers/global/config/parse/file.spec.ts b/lib/workers/global/config/parse/file.spec.ts index 822f67bda24e1a..89f92f183268f1 100644 --- a/lib/workers/global/config/parse/file.spec.ts +++ b/lib/workers/global/config/parse/file.spec.ts @@ -75,7 +75,6 @@ describe('workers/global/config/parse/file', () => { `module.exports = { "platform": "github", "token":"abcdef", - "logFileLevel": "warn", "onboarding": false, "gitAuthor": "Renovate Bot " "onboardingConfig": { diff --git a/lib/workers/global/config/parse/index.spec.ts b/lib/workers/global/config/parse/index.spec.ts index 64b0543b154310..54321079d6528e 100644 --- a/lib/workers/global/config/parse/index.spec.ts +++ b/lib/workers/global/config/parse/index.spec.ts @@ -1,13 +1,12 @@ import upath from 'upath'; import { mocked } from '../../../../../test/util'; -import { readSystemFile } from '../../../../util/fs'; +import { getParentDir, readSystemFile } from '../../../../util/fs'; import getArgv from './__fixtures__/argv'; import * as _hostRulesFromEnv from './host-rules-from-env'; jest.mock('../../../../modules/datasource/npm'); jest.mock('../../../../util/fs'); jest.mock('./host-rules-from-env'); -jest.mock('../../config.js', () => ({}), { virtual: true }); const { hostRulesFromEnv } = mocked(_hostRulesFromEnv); @@ -173,5 +172,28 @@ describe('workers/global/config/parse/index', () => { const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv); expect(parsed).toContainEntries([['dryRun', null]]); }); + + it('only initializes the file when the env var LOG_FILE is properly set', async () => { + jest.doMock('../../../../../config.js', () => ({}), { + virtual: true, + }); + const env: NodeJS.ProcessEnv = {}; + const parsedConfig = await configParser.parseConfigs(env, defaultArgv); + expect(parsedConfig).not.toContain([['logFile', 'someFile']]); + expect(getParentDir).not.toHaveBeenCalled(); + }); + + it('massage onboardingNoDeps when autodiscover is false', async () => { + jest.doMock( + '../../../../../config.js', + () => ({ onboardingNoDeps: 'auto', autodiscover: false }), + { + virtual: true, + }, + ); + const env: NodeJS.ProcessEnv = {}; + const parsedConfig = await configParser.parseConfigs(env, defaultArgv); + expect(parsedConfig).toContainEntries([['onboardingNoDeps', 'enabled']]); + }); }); }); diff --git a/lib/workers/global/config/parse/index.ts b/lib/workers/global/config/parse/index.ts index e29b7ac379903c..520321f681558a 100644 --- a/lib/workers/global/config/parse/index.ts +++ b/lib/workers/global/config/parse/index.ts @@ -1,10 +1,10 @@ import * as defaultsParser from '../../../../config/defaults'; import type { AllConfig } from '../../../../config/types'; import { mergeChildConfig } from '../../../../config/utils'; -import { addStream, logger, setContext } from '../../../../logger'; +import { logger, setContext } from '../../../../logger'; import { detectAllGlobalConfig } from '../../../../modules/manager'; import { coerceArray } from '../../../../util/array'; -import { ensureDir, getParentDir, readSystemFile } from '../../../../util/fs'; +import { readSystemFile } from '../../../../util/fs'; import { addSecretForSanitizing } from '../../../../util/sanitize'; import { ensureTrailingSlash } from '../../../../util/url'; import * as cliParser from './cli'; @@ -66,21 +66,6 @@ export async function parseConfigs( setContext(config.logContext); } - // Add file logger - // istanbul ignore if - if (config.logFile) { - logger.debug( - // TODO: types (#22198) - `Enabling ${config.logFileLevel!} logging to ${config.logFile}`, - ); - await ensureDir(getParentDir(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'); @@ -113,9 +98,11 @@ export async function parseConfigs( config.forkProcessing = 'enabled'; } - // Remove log file entries - delete config.logFile; - delete config.logFileLevel; + // Massage onboardingNoDeps + if (!config.autodiscover && config.onboardingNoDeps !== 'disabled') { + logger.debug('Enabling onboardingNoDeps while in non-autodiscover mode'); + config.onboardingNoDeps = 'enabled'; + } return config; } diff --git a/lib/workers/global/index.spec.ts b/lib/workers/global/index.spec.ts index a75815c0252165..6ee3c4a84e1859 100644 --- a/lib/workers/global/index.spec.ts +++ b/lib/workers/global/index.spec.ts @@ -45,7 +45,6 @@ describe('workers/global/index', () => { initPlatform.mockImplementation((input) => Promise.resolve(input)); delete process.env.AWS_SECRET_ACCESS_KEY; delete process.env.AWS_SESSION_TOKEN; - delete process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS; }); describe('getRepositoryConfig', () => { @@ -88,21 +87,6 @@ describe('workers/global/index', () => { expect(addSecretForSanitizing).toHaveBeenCalledTimes(2); }); - it('resolves global presets first', async () => { - process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS = 'true'; - parseConfigs.mockResolvedValueOnce({ - repositories: [], - globalExtends: [':pinVersions'], - hostRules: [{ matchHost: 'github.com', token: 'abc123' }], - }); - presets.resolveConfigPresets.mockResolvedValueOnce({}); - await expect(globalWorker.start()).resolves.toBe(0); - expect(presets.resolveConfigPresets).toHaveBeenCalledWith({ - extends: [':pinVersions'], - }); - expect(parseConfigs).toHaveBeenCalledTimes(1); - }); - it('resolves global presets immediately', async () => { parseConfigs.mockResolvedValueOnce({ repositories: [], diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts index 1585cf5c23c544..b5e6f7657dbe03 100644 --- a/lib/workers/global/index.ts +++ b/lib/workers/global/index.ts @@ -144,17 +144,10 @@ export async function start(): Promise { config = await getGlobalConfig(); if (config?.globalExtends) { // resolve global presets immediately - if (process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS) { - config = mergeChildConfig( - await resolveGlobalExtends(config.globalExtends), - config, - ); - } else { - config = mergeChildConfig( - config, - await resolveGlobalExtends(config.globalExtends), - ); - } + config = mergeChildConfig( + await resolveGlobalExtends(config.globalExtends), + config, + ); } // Set allowedHeaders in case hostRules headers are configured in file config diff --git a/lib/workers/repository/config-migration/branch/create.spec.ts b/lib/workers/repository/config-migration/branch/create.spec.ts index 175a91d57f46ce..507b6313d0623a 100644 --- a/lib/workers/repository/config-migration/branch/create.spec.ts +++ b/lib/workers/repository/config-migration/branch/create.spec.ts @@ -49,7 +49,7 @@ describe('workers/repository/config-migration/branch/create', () => { }, ], message: 'Migrate config renovate.json', - platformCommit: false, + platformCommit: 'auto', }); }); @@ -72,7 +72,7 @@ describe('workers/repository/config-migration/branch/create', () => { }, ], message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -96,7 +96,7 @@ describe('workers/repository/config-migration/branch/create', () => { }, ], message, - platformCommit: false, + platformCommit: 'auto', }); }); }); @@ -121,7 +121,7 @@ describe('workers/repository/config-migration/branch/create', () => { }, ], message, - platformCommit: false, + platformCommit: 'auto', }); }); }); @@ -147,7 +147,7 @@ describe('workers/repository/config-migration/branch/create', () => { }, ], message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -172,7 +172,7 @@ describe('workers/repository/config-migration/branch/create', () => { }, ], message, - platformCommit: false, + platformCommit: 'auto', }); }); }); diff --git a/lib/workers/repository/config-migration/branch/create.ts b/lib/workers/repository/config-migration/branch/create.ts index 202b41a4d755fd..5cbe27afeec151 100644 --- a/lib/workers/repository/config-migration/branch/create.ts +++ b/lib/workers/repository/config-migration/branch/create.ts @@ -42,6 +42,6 @@ export async function createConfigMigrationBranch( }, ], message: commitMessage.toString(), - platformCommit: !!config.platformCommit, + platformCommit: config.platformCommit, }); } diff --git a/lib/workers/repository/config-migration/branch/rebase.spec.ts b/lib/workers/repository/config-migration/branch/rebase.spec.ts index 2ad91a731f0344..2ce7be2015fc21 100644 --- a/lib/workers/repository/config-migration/branch/rebase.spec.ts +++ b/lib/workers/repository/config-migration/branch/rebase.spec.ts @@ -117,7 +117,7 @@ describe('workers/repository/config-migration/branch/rebase', () => { }, ], message: `Migrate config ${filename}`, - platformCommit: false, + platformCommit: 'auto', baseBranch: 'dev', }); }, diff --git a/lib/workers/repository/config-migration/branch/rebase.ts b/lib/workers/repository/config-migration/branch/rebase.ts index 85469195ddc4de..994e349e7908d7 100644 --- a/lib/workers/repository/config-migration/branch/rebase.ts +++ b/lib/workers/repository/config-migration/branch/rebase.ts @@ -15,8 +15,9 @@ export async function rebaseMigrationBranch( migratedConfigData: MigratedData, ): Promise { logger.debug('Checking if migration branch needs rebasing'); + const baseBranch = config.defaultBranch!; const branchName = getMigrationBranchName(config); - if (await scm.isBranchModified(branchName)) { + if (await scm.isBranchModified(branchName, baseBranch)) { logger.debug('Migration branch has been edited and cannot be rebased'); return null; } @@ -42,7 +43,7 @@ export async function rebaseMigrationBranch( ); const commitMessage = commitMessageFactory.getCommitMessage(); - await scm.checkoutBranch(config.defaultBranch!); + await scm.checkoutBranch(baseBranch); contents = await MigratedDataFactory.applyPrettierFormatting(migratedConfigData); return scm.commitAndPush({ @@ -56,7 +57,7 @@ export async function rebaseMigrationBranch( }, ], message: commitMessage.toString(), - platformCommit: !!config.platformCommit, + platformCommit: config.platformCommit, }); } diff --git a/lib/workers/repository/finalize/prune.ts b/lib/workers/repository/finalize/prune.ts index e46e24ae7505dd..ae904b854bafc8 100644 --- a/lib/workers/repository/finalize/prune.ts +++ b/lib/workers/repository/finalize/prune.ts @@ -25,7 +25,10 @@ async function cleanUpBranches( state: 'open', targetBranch: config.baseBranch, }); - const branchIsModified = await scm.isBranchModified(branchName); + const branchIsModified = await scm.isBranchModified( + branchName, + config.defaultBranch!, + ); if (pr) { if (branchIsModified) { logger.debug( diff --git a/lib/workers/repository/index.ts b/lib/workers/repository/index.ts index f9d30bcf787aef..9b05fd8420af5b 100644 --- a/lib/workers/repository/index.ts +++ b/lib/workers/repository/index.ts @@ -16,7 +16,6 @@ import { removeDanglingContainers } from '../../util/exec/docker'; import { deleteLocalFile, privateCacheDir } from '../../util/fs'; import { isCloned } from '../../util/git'; import { detectSemanticCommits } from '../../util/git/semantic'; -import { clearDnsCache, printDnsStats } from '../../util/http/dns'; import * as queue from '../../util/http/queue'; import * as throttle from '../../util/http/throttle'; import { addSplit, getSplits, splitInit } from '../../util/split'; @@ -138,8 +137,6 @@ export async function renovateRepository( HttpStats.report(); HttpCacheStats.report(); LookupStats.report(); - printDnsStats(); - clearDnsCache(); const cloned = isCloned(); logger.info({ cloned, durationMs: splits.total }, 'Repository finished'); resetRepositoryLogLevelRemaps(); diff --git a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap index 3722c216ecee88..b858569a3ee6b1 100644 --- a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap +++ b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap @@ -1,46 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() returns github actions alerts 1`] = ` -[ - { - "allowedVersions": "1.8.3", - "force": { - "branchTopic": "{{{datasource}}}-{{{depName}}}-vulnerability", - "commitMessageSuffix": "[SECURITY]", - "dependencyDashboardApproval": false, - "groupName": null, - "minimumReleaseAge": null, - "prCreation": "immediate", - "rangeStrategy": "update-lockfile", - "schedule": [], - }, - "isVulnerabilityAlert": true, - "matchCurrentVersion": "1.8.2", - "matchDatasources": [ - "github-tags", - ], - "matchFileNames": [ - ".github/workflows/build.yaml", - ], - "matchPackageNames": [ - "bar", - ], - "prBodyNotes": [ - "### GitHub Vulnerability Alerts", - "#### [def]() - -actions", - ], - }, -] -`; - exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() returns go alerts 1`] = ` [ { "allowedVersions": "1.8.3", "force": { - "branchTopic": "{{{datasource}}}-{{{depName}}}-vulnerability", + "branchTopic": "{{{datasource}}}-{{{depNameSanitized}}}-vulnerability", "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, @@ -50,7 +15,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur "schedule": [], }, "isVulnerabilityAlert": true, - "matchCurrentVersion": "= 1.8.2", + "matchCurrentVersion": "< 1.8.3", "matchDatasources": [ "go", ], @@ -75,7 +40,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur { "allowedVersions": "2.7.9.4", "force": { - "branchTopic": "{{{datasource}}}-{{{depName}}}-vulnerability", + "branchTopic": "{{{datasource}}}-{{{depNameSanitized}}}-vulnerability", "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, @@ -85,7 +50,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur "schedule": [], }, "isVulnerabilityAlert": true, - "matchCurrentVersion": "2.4.2", + "matchCurrentVersion": "(,2.7.9.4)", "matchDatasources": [ "maven", ], @@ -110,7 +75,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur { "allowedVersions": "==2.2.1.0", "force": { - "branchTopic": "{{{datasource}}}-{{{depName}}}-vulnerability", + "branchTopic": "{{{datasource}}}-{{{depNameSanitized}}}-vulnerability", "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, @@ -120,7 +85,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur "schedule": [], }, "isVulnerabilityAlert": true, - "matchCurrentVersion": "== 1.6.7", + "matchCurrentVersion": "< 2.2.1.0", "matchDatasources": [ "pypi", ], @@ -154,22 +119,3 @@ Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validati }, ] `; - -exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() returns remediations 1`] = ` -{ - "backend/package-lock.json": [ - { - "currentVersion": "1.8.2", - "datasource": "npm", - "depName": "electron", - "newVersion": "1.8.3", - "prBodyNotes": [ - "### GitHub Vulnerability Alerts", - "#### [GHSA-8xwg-wv7v-4vqp](https://nvd.nist.gov/vuln/detail/CVE-2018-1000136) - -Electron version 1.7 up to 1.7.12; 1.8 up to 1.8.3 and 2.0.0 up to 2.0.0-beta.3 contains an improper handling of values vulnerability in Webviews that can result in remote code execution. This attack appear to be exploitable via an app which allows execution of 3rd party code AND disallows node integration AND has not specified if webview is enabled/disabled. This vulnerability appears to have been fixed in 1.7.13, 1.8.4, 2.0.0-beta.4.", - ], - }, - ], -} -`; diff --git a/lib/workers/repository/init/vulnerability.spec.ts b/lib/workers/repository/init/vulnerability.spec.ts index db6ca1ca50bf87..c6921021f4cc32 100644 --- a/lib/workers/repository/init/vulnerability.spec.ts +++ b/lib/workers/repository/init/vulnerability.spec.ts @@ -1,6 +1,7 @@ import { RenovateConfig, partial, platform } from '../../../../test/util'; import { getConfig } from '../../../config/defaults'; import { NO_VULNERABILITY_ALERTS } from '../../../constants/error-messages'; +import { logger } from '../../../logger'; import type { VulnerabilityAlert } from '../../../types'; import { detectVulnerabilityAlerts } from './vulnerability'; @@ -38,42 +39,16 @@ describe('workers/repository/init/vulnerability', () => { ); }); - it('ignores yargs-parser special case', async () => { - // TODO #22198 - delete config.vulnerabilityAlerts!.enabled; - delete config.packageRules; // test coverage - platform.getVulnerabilityAlerts.mockResolvedValue([ - partial(), - { - // this will be ignored - dismissReason: null, - vulnerableManifestFilename: 'package-lock.json', - vulnerableManifestPath: 'backend/package-lock.json', - securityAdvisory: { - references: [], - severity: '', - }, - securityVulnerability: { - package: { ecosystem: 'NPM', name: 'yargs-parser' }, - vulnerableVersionRange: '>5.0.0-security.0', - }, - vulnerableRequirements: '= 5.0.1', - }, - ]); - const res = await detectVulnerabilityAlerts(config); - expect(res.packageRules).toHaveLength(0); - }); - - it('ignores alert if dismissReason is not null', async () => { + it('ignores alert if dismissed_reason is not nullish', async () => { // TODO #22198 delete config.vulnerabilityAlerts!.enabled; platform.getVulnerabilityAlerts.mockResolvedValue([ { - dismissReason: 'some reason', - vulnerableManifestFilename: 'package-lock.json', - vulnerableManifestPath: 'package-lock.json', - vulnerableRequirements: '= 1.8.2', - securityAdvisory: { + dismissed_reason: 'some reason', + dependency: { + manifest_path: 'package-lock.json', + }, + security_advisory: { description: 'GitHub Electron 1.7.15, 1.8.7, 2.0.7, and 3.0.0-beta.6, in certain scenarios involving IFRAME elements and "nativeWindowOpen: true" or "sandbox: true" options, is affected by a WebPreferences vulnerability that can be leveraged to perform remote code execution.', identifiers: [ @@ -83,12 +58,11 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2018-15685' }, ], - severity: 'HIGH', }, - securityVulnerability: { - package: { name: 'electron', ecosystem: 'NPM' }, - firstPatchedVersion: { identifier: '1.8.8' }, - vulnerableVersionRange: '>= 1.8.0, < 1.8.8', + security_vulnerability: { + package: { name: 'electron', ecosystem: 'npm' }, + first_patched_version: { identifier: '1.8.8' }, + vulnerable_version_range: '>= 1.8.0, < 1.8.8', }, }, ]); @@ -102,11 +76,11 @@ describe('workers/repository/init/vulnerability', () => { platform.getVulnerabilityAlerts.mockResolvedValue([ { // will be ignored - no firstPatchVersion - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'requirements.txt', + }, + security_advisory: { description: 'The create_script function in the lxc_container module in Ansible before 1.9.6-1 and 2.x before 2.0.2.0 allows local users to write to arbitrary files or gain privileges via a symlink attack on (1) /opt/.lxc-attach-script, (2) the archived container in the archive_path directory, or the (3) lxc-attach-script.log or (4) lxc-attach-script.err files in the temporary directory.', identifiers: [ @@ -116,11 +90,10 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-3096' }, ], - severity: 'HIGH', }, - securityVulnerability: { - package: { name: 'ansible', ecosystem: 'PIP' }, - vulnerableVersionRange: '< 1.9.6.1', + security_vulnerability: { + package: { name: 'ansible', ecosystem: 'pip' }, + vulnerable_version_range: '< 1.9.6.1', }, }, ]); @@ -128,26 +101,26 @@ describe('workers/repository/init/vulnerability', () => { expect(res.packageRules).toHaveLength(0); }); - it('returns github actions alerts', async () => { + it('returns go alerts', async () => { + // TODO #22198 delete config.vulnerabilityAlerts!.enabled; + delete config.packageRules; // coverage platform.getVulnerabilityAlerts.mockResolvedValue([ partial(), - { - dismissReason: null, - vulnerableManifestFilename: '.github/workflows/build.yaml', - vulnerableManifestPath: '.github/workflows/build.yaml', - vulnerableRequirements: '= 1.8.2', - securityAdvisory: { - description: 'actions', - identifiers: [{ type: 'GHSA', value: 'def' }], + dismissed_reason: null, + dependency: { + manifest_path: 'go.sum', + }, + security_advisory: { + description: 'go', + identifiers: [{ type: 'GHSA', value: 'abc' }], references: [{ url: '' }], - severity: 'HIGH', }, - securityVulnerability: { - package: { name: 'bar', ecosystem: 'ACTIONS' }, - firstPatchedVersion: { identifier: '1.8.3' }, - vulnerableVersionRange: '>= 1.8, < 1.8.3', + security_vulnerability: { + package: { name: 'foo', ecosystem: 'go' }, + first_patched_version: { identifier: '1.8.3' }, + vulnerable_version_range: '>= 1.8, < 1.8.3', }, }, ]); @@ -156,32 +129,33 @@ describe('workers/repository/init/vulnerability', () => { expect(res.packageRules).toHaveLength(1); }); - it('returns go alerts', async () => { + // coverage + it('warns if any error occurs while parsing the alerts', async () => { // TODO #22198 delete config.vulnerabilityAlerts!.enabled; platform.getVulnerabilityAlerts.mockResolvedValue([ partial(), { - dismissReason: null, - vulnerableManifestFilename: 'go.sum', - vulnerableManifestPath: 'go.sum', - vulnerableRequirements: '= 1.8.2', - securityAdvisory: { + dismissed_reason: null, + // @ts-expect-error invalid options + dependency: {}, + security_advisory: { description: 'go', identifiers: [{ type: 'GHSA', value: 'abc' }], references: [{ url: '' }], - severity: 'HIGH', }, - securityVulnerability: { - package: { name: 'foo', ecosystem: 'GO' }, - firstPatchedVersion: { identifier: '1.8.3' }, - vulnerableVersionRange: '>= 1.8, < 1.8.3', + security_vulnerability: { + package: { name: 'foo', ecosystem: 'go' }, + first_patched_version: { identifier: '1.8.3' }, + vulnerable_version_range: '>= 1.8, < 1.8.3', }, }, ]); - const res = await detectVulnerabilityAlerts(config); - expect(res.packageRules).toMatchSnapshot(); - expect(res.packageRules).toHaveLength(1); + await detectVulnerabilityAlerts(config); + expect(logger.warn).toHaveBeenCalledWith( + { err: expect.any(Object) }, + 'Error parsing vulnerability alert', + ); }); it('returns maven alerts', async () => { @@ -189,11 +163,11 @@ describe('workers/repository/init/vulnerability', () => { delete config.vulnerabilityAlerts!.enabled; platform.getVulnerabilityAlerts.mockResolvedValue([ { - dismissReason: null, - vulnerableManifestFilename: 'pom.xml', - vulnerableManifestPath: 'pom.xml', - vulnerableRequirements: '= 2.4.2', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'pom.xml', + }, + security_advisory: { description: 'An issue was discovered in FasterXML jackson-databind prior to 2.7.9.4, 2.8.11.2, and 2.9.6. When Default Typing is enabled (either globally or for a specific property), the service has the Jodd-db jar (for database access for the Jodd framework) in the classpath, and an attacker can provide an LDAP service to access, it is possible to make the service execute a malicious payload.', identifiers: [ @@ -203,15 +177,14 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2018-12022' }, ], - severity: 'HIGH', }, - securityVulnerability: { + security_vulnerability: { package: { name: 'com.fasterxml.jackson.core:jackson-databind', - ecosystem: 'MAVEN', + ecosystem: 'maven', }, - firstPatchedVersion: { identifier: '2.7.9.4' }, - vulnerableVersionRange: '< 2.7.9.4', + first_patched_version: { identifier: '2.7.9.4' }, + vulnerable_version_range: '< 2.7.9.4', }, }, ]); @@ -225,11 +198,11 @@ describe('workers/repository/init/vulnerability', () => { delete config.vulnerabilityAlerts!.enabled; platform.getVulnerabilityAlerts.mockResolvedValue([ { - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'requirements.txt', + }, + security_advisory: { description: "Ansible before versions 2.3.1.0 and 2.4.0.0 fails to properly mark lookup-plugin results as unsafe. If an attacker could control the results of lookup() calls, they could inject Unicode strings to be parsed by the jinja2 templating system, resulting in code execution. By default, the jinja2 templating language is now marked as 'unsafe' and is not evaluated.", identifiers: [ @@ -239,20 +212,19 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2017-7481' }, ], - severity: 'MODERATE', }, - securityVulnerability: { - package: { name: 'ansible', ecosystem: 'PIP' }, - firstPatchedVersion: { identifier: 'abc-2.3.1.0' }, - vulnerableVersionRange: '< 2.3.1.0', + security_vulnerability: { + package: { name: 'ansible', ecosystem: 'pip' }, + first_patched_version: { identifier: 'abc-2.3.1.0' }, + vulnerable_version_range: '< 2.3.1.0', }, }, { - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'requirements.txt', + }, + security_advisory: { description: "Ansible before 1.9.2 does not verify that the server hostname matches a domain name in the subject's Common Name (CN) or subjectAltName field of the X.509 certificate, which allows man-in-the-middle attackers to spoof SSL servers via an arbitrary valid certificate.", identifiers: [ @@ -262,20 +234,19 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2015-3908' }, ], - severity: 'MODERATE', }, - securityVulnerability: { - package: { name: 'ansible', ecosystem: 'PIP' }, - firstPatchedVersion: { identifier: '1.9.2' }, - vulnerableVersionRange: '< 1.9.2', + security_vulnerability: { + package: { name: 'ansible', ecosystem: 'pip' }, + first_patched_version: { identifier: '1.9.2' }, + vulnerable_version_range: '< 1.9.2', }, }, { - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'requirements.txt', + }, + security_advisory: { description: "An input validation vulnerability was found in Ansible's mysql_user module before 2.2.1.0, which may fail to correctly change a password in certain circumstances. Thus the previous password would still be active when it should have been changed.", identifiers: [ @@ -285,20 +256,19 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-8647' }, ], - severity: 'MODERATE', }, - securityVulnerability: { - package: { name: 'ansible', ecosystem: 'PIP' }, - firstPatchedVersion: { identifier: '2.2.1.0' }, - vulnerableVersionRange: '< 2.2.1.0', + security_vulnerability: { + package: { name: 'ansible', ecosystem: 'pip' }, + first_patched_version: { identifier: '2.2.1.0' }, + vulnerable_version_range: '< 2.2.1.0', }, }, { - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'requirements.txt', + }, + security_advisory: { description: 'A flaw was found in Ansible before version 2.2.0. The apt_key module does not properly verify key fingerprints, allowing remote adversary to create an OpenPGP key which matches the short key ID and inject this key instead of the correct key.', identifiers: [ @@ -308,20 +278,19 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-8614' }, ], - severity: 'MODERATE', }, - securityVulnerability: { - package: { name: 'ansible', ecosystem: 'PIP' }, - firstPatchedVersion: { identifier: '2.2.0' }, - vulnerableVersionRange: '< 2.2.0', + security_vulnerability: { + package: { name: 'ansible', ecosystem: 'pip' }, + first_patched_version: { identifier: '2.2.0' }, + vulnerable_version_range: '< 2.2.0', }, }, { - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'requirements.txt', + }, + security_advisory: { description: 'Ansible before version 2.2.0 fails to properly sanitize fact variables sent from the Ansible controller. An attacker with the ability to create special variables on the controller could execute arbitrary commands on Ansible clients as the user Ansible runs as.', identifiers: [ @@ -331,20 +300,19 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-8628' }, ], - severity: 'MODERATE', }, - securityVulnerability: { - package: { name: 'ansible', ecosystem: 'PIP' }, - firstPatchedVersion: { identifier: '2.2.0' }, - vulnerableVersionRange: '< 2.2.0', + security_vulnerability: { + package: { name: 'ansible', ecosystem: 'pip' }, + first_patched_version: { identifier: '2.2.0' }, + vulnerable_version_range: '< 2.2.0', }, }, { - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { + dismissed_reason: null, + dependency: { + manifest_path: 'requirements.txt', + }, + security_advisory: { description: "Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validation in Ansible's handling of data sent from client systems. An attacker with control over a client system being managed by Ansible and the ability to send facts back to the Ansible server could use this flaw to execute arbitrary code on the Ansible server using the Ansible server privileges.", identifiers: [ @@ -354,12 +322,11 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-9587' }, ], - severity: 'MODERATE', }, - securityVulnerability: { - package: { name: 'ansible', ecosystem: 'PIP' }, - firstPatchedVersion: { identifier: '2.1.4' }, - vulnerableVersionRange: '< 2.1.4', + security_vulnerability: { + package: { name: 'ansible', ecosystem: 'pip' }, + first_patched_version: { identifier: '2.1.4' }, + vulnerable_version_range: '< 2.1.4', }, }, ]); @@ -367,113 +334,5 @@ describe('workers/repository/init/vulnerability', () => { expect(res.packageRules).toMatchSnapshot(); expect(res.packageRules).toHaveLength(1); }); - - it('returns pip alerts with normalized name', async () => { - // TODO #22198 - delete config.vulnerabilityAlerts!.enabled; - platform.getVulnerabilityAlerts.mockResolvedValue([ - { - dismissReason: null, - vulnerableManifestFilename: 'requirements.txt', - vulnerableManifestPath: 'requirements.txt', - vulnerableRequirements: '= 1.6.7', - securityAdvisory: { - description: 'Description', - identifiers: [ - { type: 'GHSA', value: 'GHSA-m956-frf4-m2wr' }, - { type: 'CVE', value: 'CVE-2016-2137' }, - ], - references: [ - { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-9587' }, - ], - severity: 'MODERATE', - }, - securityVulnerability: { - package: { name: 'Pillow', ecosystem: 'PIP' }, - firstPatchedVersion: { identifier: '2.1.4' }, - vulnerableVersionRange: '< 2.1.4', - }, - }, - ]); - const res = await detectVulnerabilityAlerts(config); - expect(res.packageRules).toHaveLength(1); - expect(res.packageRules![0].matchPackageNames).toEqual([ - 'Pillow', - 'pillow', - ]); - }); - - it('returns remediations', async () => { - config.transitiveRemediation = true; - // TODO #22198 - delete config.vulnerabilityAlerts!.enabled; - platform.getVulnerabilityAlerts.mockResolvedValue([ - partial(), - { - dismissReason: null, - vulnerableManifestFilename: 'package-lock.json', - vulnerableManifestPath: 'backend/package-lock.json', - vulnerableRequirements: '= 1.8.2', - securityAdvisory: { - description: - 'Electron version 1.7 up to 1.7.12; 1.8 up to 1.8.3 and 2.0.0 up to 2.0.0-beta.3 contains an improper handling of values vulnerability in Webviews that can result in remote code execution. This attack appear to be exploitable via an app which allows execution of 3rd party code AND disallows node integration AND has not specified if webview is enabled/disabled. This vulnerability appears to have been fixed in 1.7.13, 1.8.4, 2.0.0-beta.4.', - identifiers: [{ type: 'GHSA', value: 'GHSA-8xwg-wv7v-4vqp' }], - references: [ - { url: 'https://nvd.nist.gov/vuln/detail/CVE-2018-1000136' }, - ], - severity: 'HIGH', - }, - securityVulnerability: { - package: { name: 'electron', ecosystem: 'NPM' }, - firstPatchedVersion: { identifier: '1.8.3' }, - vulnerableVersionRange: '>= 1.8, < 1.8.3', - }, - }, - ]); - const res = await detectVulnerabilityAlerts(config); - expect(res.packageRules).toHaveLength(1); - expect(res.remediations).toMatchSnapshot({ - 'backend/package-lock.json': [ - { - currentVersion: '1.8.2', - datasource: 'npm', - depName: 'electron', - newVersion: '1.8.3', - }, - ], - }); - }); - - it('ignores unsupported remediation file types', async () => { - config.transitiveRemediation = true; - // TODO #22198 - delete config.vulnerabilityAlerts!.enabled; - platform.getVulnerabilityAlerts.mockResolvedValue([ - partial(), - { - dismissReason: null, - vulnerableManifestFilename: 'package.json', - vulnerableManifestPath: 'backend/package.json', - vulnerableRequirements: '= 1.8.2', - securityAdvisory: { - description: - 'Electron version 1.7 up to 1.7.12; 1.8 up to 1.8.3 and 2.0.0 up to 2.0.0-beta.3 contains an improper handling of values vulnerability in Webviews that can result in remote code execution. This attack appear to be exploitable via an app which allows execution of 3rd party code AND disallows node integration AND has not specified if webview is enabled/disabled. This vulnerability appears to have been fixed in 1.7.13, 1.8.4, 2.0.0-beta.4.', - identifiers: [{ type: 'GHSA', value: 'GHSA-8xwg-wv7v-4vqp' }], - references: [ - { url: 'https://nvd.nist.gov/vuln/detail/CVE-2018-1000136' }, - ], - severity: 'HIGH', - }, - securityVulnerability: { - package: { name: 'electron', ecosystem: 'NPM' }, - firstPatchedVersion: { identifier: '1.8.3' }, - vulnerableVersionRange: '>= 1.8, < 1.8.3', - }, - }, - ]); - const res = await detectVulnerabilityAlerts(config); - expect(res.packageRules).toHaveLength(1); - expect(res.remediations).toBeEmptyObject(); - }); }); }); diff --git a/lib/workers/repository/init/vulnerability.ts b/lib/workers/repository/init/vulnerability.ts index ed3efe653f8267..e276e927e3b06e 100644 --- a/lib/workers/repository/init/vulnerability.ts +++ b/lib/workers/repository/init/vulnerability.ts @@ -2,14 +2,12 @@ import type { PackageRule, RenovateConfig } from '../../../config/types'; import { NO_VULNERABILITY_ALERTS } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { CrateDatasource } from '../../../modules/datasource/crate'; -import { GithubTagsDatasource } from '../../../modules/datasource/github-tags'; import { GoDatasource } from '../../../modules/datasource/go'; import { MavenDatasource } from '../../../modules/datasource/maven'; import { NpmDatasource } from '../../../modules/datasource/npm'; import { NugetDatasource } from '../../../modules/datasource/nuget'; import { PackagistDatasource } from '../../../modules/datasource/packagist'; import { PypiDatasource } from '../../../modules/datasource/pypi'; -import { normalizePythonDepName } from '../../../modules/datasource/pypi/common'; import { RubyGemsDatasource } from '../../../modules/datasource/rubygems'; import { platform } from '../../../modules/platform'; import * as allVersioning from '../../../modules/versioning'; @@ -26,7 +24,6 @@ import { regEx } from '../../../util/regex'; type Datasource = string; type DependencyName = string; type FileName = string; -type VulnerableRequirements = string; type CombinedAlert = Record< FileName, @@ -34,14 +31,11 @@ type CombinedAlert = Record< Datasource, Record< DependencyName, - Record< - VulnerableRequirements, - { - advisories: SecurityAdvisory[]; - fileType?: string; - firstPatchedVersion?: string; - } - > + { + advisories: SecurityAdvisory[]; + fileType?: string; + firstPatchedVersion?: string; + } > > >; @@ -78,18 +72,11 @@ export async function detectVulnerabilityAlerts( }; const combinedAlerts: CombinedAlert = {}; for (const alert of alerts) { - if ( - alert.securityVulnerability?.package?.name === 'yargs-parser' && - (alert.vulnerableRequirements === '= 5.0.0-security.0' || - alert.vulnerableRequirements === '= 5.0.1') - ) { - continue; - } try { - if (alert.dismissReason) { + if (alert.dismissed_reason) { continue; } - if (!alert.securityVulnerability.firstPatchedVersion) { + if (!alert.security_vulnerability?.first_patched_version) { logger.debug( { alert }, 'Vulnerability alert has no firstPatchedVersion - skipping', @@ -97,56 +84,30 @@ export async function detectVulnerabilityAlerts( continue; } const datasourceMapping: Record = { - ACTIONS: GithubTagsDatasource.id, - COMPOSER: PackagistDatasource.id, - GO: GoDatasource.id, - MAVEN: MavenDatasource.id, - NPM: NpmDatasource.id, - NUGET: NugetDatasource.id, - PIP: PypiDatasource.id, - RUBYGEMS: RubyGemsDatasource.id, - RUST: CrateDatasource.id, + composer: PackagistDatasource.id, + go: GoDatasource.id, + maven: MavenDatasource.id, + npm: NpmDatasource.id, + nuget: NugetDatasource.id, + pip: PypiDatasource.id, + rubygems: RubyGemsDatasource.id, + rust: CrateDatasource.id, }; const datasource = - datasourceMapping[alert.securityVulnerability.package.ecosystem]; - const depName = alert.securityVulnerability.package.name; - const fileName = alert.vulnerableManifestPath; - const fileType = alert.vulnerableManifestFilename; + datasourceMapping[alert.security_vulnerability.package.ecosystem]; + const depName = alert.security_vulnerability.package.name; + const fileName = alert.dependency.manifest_path; + const fileType = fileName.split('/').pop(); const firstPatchedVersion = - alert.securityVulnerability.firstPatchedVersion.identifier; - const advisory = alert.securityAdvisory; - // TODO #22198 - let vulnerableRequirements = alert.vulnerableRequirements!; - // istanbul ignore if - if (!vulnerableRequirements.length) { - if (datasource === MavenDatasource.id) { - vulnerableRequirements = `(,${firstPatchedVersion})`; - } else { - vulnerableRequirements = `< ${firstPatchedVersion}`; - } - } - if (datasource === PypiDatasource.id) { - vulnerableRequirements = vulnerableRequirements.replace( - regEx(/^= /), - '== ', - ); - } - if ( - datasource === GithubTagsDatasource.id || - datasource === MavenDatasource.id - ) { - // GitHub Actions uses docker versioning, which doesn't support `= 1.2.3` matching, so we strip the equals - vulnerableRequirements = vulnerableRequirements.replace(/^=\s*/, ''); - } + alert.security_vulnerability.first_patched_version.identifier; + const advisory = alert.security_advisory; + combinedAlerts[fileName] ||= {}; combinedAlerts[fileName][datasource] ||= {}; - combinedAlerts[fileName][datasource][depName] ||= {}; - combinedAlerts[fileName][datasource][depName][vulnerableRequirements] ||= - { - advisories: [], - }; - const alertDetails = - combinedAlerts[fileName][datasource][depName][vulnerableRequirements]; + combinedAlerts[fileName][datasource][depName] ||= { + advisories: [], + }; + const alertDetails = combinedAlerts[fileName][datasource][depName]; alertDetails.advisories.push(advisory); const version = allVersioning.get(versionings[datasource]); if (version.isVersion(firstPatchedVersion)) { @@ -171,101 +132,84 @@ export async function detectVulnerabilityAlerts( config.remediations = {} as never; for (const [fileName, files] of Object.entries(combinedAlerts)) { for (const [datasource, dependencies] of Object.entries(files)) { - for (const [depName, currentValues] of Object.entries(dependencies)) { - for (const [matchCurrentVersion, val] of Object.entries( - currentValues, - )) { - let prBodyNotes: string[] = []; - try { - prBodyNotes = ['### GitHub Vulnerability Alerts'].concat( - val.advisories.map((advisory) => { - const identifiers = advisory.identifiers!; - const description = advisory.description!; - let content = '#### '; - let heading: string; - if (identifiers.some((id) => id.type === 'CVE')) { - heading = identifiers - .filter((id) => id.type === 'CVE') - .map((id) => id.value) - .join(' / '); - } else { - heading = identifiers.map((id) => id.value).join(' / '); - } - if (advisory.references.length) { - heading = `[${heading}](${advisory.references[0].url})`; - } - content += heading; - content += '\n\n'; + for (const [depName, val] of Object.entries(dependencies)) { + let prBodyNotes: string[] = []; + try { + prBodyNotes = ['### GitHub Vulnerability Alerts'].concat( + val.advisories.map((advisory) => { + const identifiers = advisory.identifiers; + const description = advisory.description; + let content = '#### '; + let heading: string; + if (identifiers.some((id) => id.type === 'CVE')) { + heading = identifiers + .filter((id) => id.type === 'CVE') + .map((id) => id.value) + .join(' / '); + } else { + heading = identifiers.map((id) => id.value).join(' / '); + } + if (advisory.references?.length) { + heading = `[${heading}](${advisory.references[0].url})`; + } + content += heading; + content += '\n\n'; - content += sanitizeMarkdown(description); - return content; - }), - ); - } catch (err) /* istanbul ignore next */ { - logger.warn({ err }, 'Error generating vulnerability PR notes'); - } - // TODO: types (#22198) - const allowedVersions = - datasource === PypiDatasource.id - ? `==${val.firstPatchedVersion!}` - : val.firstPatchedVersion; - const matchFileNames = - datasource === GoDatasource.id - ? [fileName.replace('go.sum', 'go.mod')] - : [fileName]; - let matchRule: PackageRule = { - matchDatasources: [datasource], - matchPackageNames: [depName], - matchCurrentVersion, - matchFileNames, - }; - if ( - datasource === PypiDatasource.id && - normalizePythonDepName(depName) !== depName - ) { - matchRule.matchPackageNames?.push(normalizePythonDepName(depName)); - } - const supportedRemediationFileTypes = ['package-lock.json']; - if ( - config.transitiveRemediation && - supportedRemediationFileTypes.includes(val.fileType!) - ) { - const remediations = config.remediations as Record< - string, - unknown[] - >; - remediations[fileName] ??= []; - const currentVersion = matchCurrentVersion.replace('=', '').trim(); - const newVersion = allowedVersions; - const remediation = { - datasource, - depName, - currentVersion, - newVersion, - prBodyNotes, - }; - remediations[fileName].push(remediation); + content += sanitizeMarkdown(description); + return content; + }), + ); + } catch (err) /* istanbul ignore next */ { + logger.warn({ err }, 'Error generating vulnerability PR notes'); + } + + const allowedVersions = + datasource === PypiDatasource.id + ? `==${val.firstPatchedVersion}` + : val.firstPatchedVersion; + const matchFileNames = + datasource === GoDatasource.id + ? [fileName.replace('go.sum', 'go.mod')] + : [fileName]; + let matchCurrentVersion = ''; + if (!matchCurrentVersion.length) { + if (datasource === MavenDatasource.id) { + matchCurrentVersion = `(,${val.firstPatchedVersion})`; } else { - // Remediate only direct dependencies - matchRule = { - ...matchRule, - allowedVersions, - prBodyNotes, - isVulnerabilityAlert: true, - force: { - ...config.vulnerabilityAlerts, - }, - }; - // istanbul ignore if - if ( - config.transitiveRemediation && - matchRule.matchFileNames?.[0] === 'package.json' - ) { - matchRule.force!.rangeStrategy = 'replace'; - } + matchCurrentVersion = `< ${val.firstPatchedVersion}`; } - alertPackageRules.push(matchRule); } + + switch (datasource) { + case PypiDatasource.id: + matchCurrentVersion = matchCurrentVersion.replace( + regEx(/^= /), + '== ', + ); + break; + case MavenDatasource.id: + matchCurrentVersion = matchCurrentVersion.replace(/^=\s*/, ''); + break; + } + + let matchRule: PackageRule = { + matchDatasources: [datasource], + matchPackageNames: [depName], + matchCurrentVersion, + matchFileNames, + }; + + // Remediate only direct dependencies + matchRule = { + ...matchRule, + allowedVersions, + prBodyNotes, + isVulnerabilityAlert: true, + force: { + ...config.vulnerabilityAlerts, + }, + }; + alertPackageRules.push(matchRule); } } } diff --git a/lib/workers/repository/onboarding/branch/create.spec.ts b/lib/workers/repository/onboarding/branch/create.spec.ts index db9b3b8ddf75a4..2d63a39461e459 100644 --- a/lib/workers/repository/onboarding/branch/create.spec.ts +++ b/lib/workers/repository/onboarding/branch/create.spec.ts @@ -30,7 +30,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message: 'Add renovate.json', - platformCommit: false, + platformCommit: 'auto', }); }); @@ -53,7 +53,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -74,7 +74,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message: `Add renovate.json\n\nsome commit body`, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -100,7 +100,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message: `We can Renovate if we want to, we can leave PRs in decline\n\nSigned Off: `, - platformCommit: false, + platformCommit: 'auto', }); }); }); @@ -125,7 +125,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -153,7 +153,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); }); @@ -178,7 +178,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -206,7 +206,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); }); @@ -232,7 +232,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -256,7 +256,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -281,7 +281,7 @@ describe('workers/repository/onboarding/branch/create', () => { ], force: true, message, - platformCommit: false, + platformCommit: 'auto', }); }); @@ -300,7 +300,7 @@ describe('workers/repository/onboarding/branch/create', () => { files: [{ type: 'addition', path, contents: '{"foo":"bar"}' }], message, force: true, - platformCommit: false, + platformCommit: 'auto', }); }); }); diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts index 8070cfed2a5831..a3eb77c9c79804 100644 --- a/lib/workers/repository/onboarding/branch/create.ts +++ b/lib/workers/repository/onboarding/branch/create.ts @@ -61,7 +61,7 @@ export async function createOnboardingBranch( }, ], message: commitMessage, - platformCommit: !!config.platformCommit, + platformCommit: config.platformCommit, force: true, }); } diff --git a/lib/workers/repository/onboarding/branch/index.spec.ts b/lib/workers/repository/onboarding/branch/index.spec.ts index 814513a213bb73..877aa6e42607cb 100644 --- a/lib/workers/repository/onboarding/branch/index.spec.ts +++ b/lib/workers/repository/onboarding/branch/index.spec.ts @@ -59,7 +59,7 @@ describe('workers/repository/onboarding/branch/index', () => { }); it("doesn't throw if there are no package files and onboardingNoDeps config option is set", async () => { - config.onboardingNoDeps = true; + config.onboardingNoDeps = 'enabled'; await expect(checkOnboardingBranch(config)).resolves.not.toThrow( REPOSITORY_NO_PACKAGE_FILES, ); diff --git a/lib/workers/repository/onboarding/branch/index.ts b/lib/workers/repository/onboarding/branch/index.ts index 488d75a0c6ea77..5090b9c847f118 100644 --- a/lib/workers/repository/onboarding/branch/index.ts +++ b/lib/workers/repository/onboarding/branch/index.ts @@ -32,6 +32,7 @@ export async function checkOnboardingBranch( logger.debug('checkOnboarding()'); logger.trace({ config }); let onboardingBranch = config.onboardingBranch; + const defaultBranch = config.defaultBranch!; let isConflicted = false; let isModified = false; const repoIsOnboarded = await isOnboarded(config); @@ -54,7 +55,10 @@ export async function checkOnboardingBranch( if (onboardingPr) { logger.debug('Onboarding PR already exists'); - isModified = await isOnboardingBranchModified(config.onboardingBranch!); + isModified = await isOnboardingBranchModified( + config.onboardingBranch!, + defaultBranch, + ); // if onboarding branch is not modified, check if onboarding config has been changed and rebase if true if (!isModified) { const commit = await rebaseOnboardingBranch( @@ -78,7 +82,7 @@ export async function checkOnboardingBranch( if ( isConfigHashPresent(onboardingPr) && // needed so that existing onboarding PRs are updated with config hash comment - isOnboardingCacheValid(config.defaultBranch!, config.onboardingBranch!) && + isOnboardingCacheValid(defaultBranch, config.onboardingBranch!) && !(config.onboardingRebaseCheckbox && OnboardingState.prUpdateRequested) ) { logger.debug( @@ -108,7 +112,7 @@ export async function checkOnboardingBranch( Object.entries((await extractAllDependencies(mergedConfig)).packageFiles) .length === 0 ) { - if (!config?.onboardingNoDeps) { + if (config.onboardingNoDeps !== 'enabled') { throw new Error(REPOSITORY_NO_PACKAGE_FILES); } } diff --git a/lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts index 4f0a385eb595ee..ed9e9746d48af3 100644 --- a/lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts +++ b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.spec.ts @@ -147,7 +147,7 @@ describe('workers/repository/onboarding/branch/onboarding-branch-cache', () => { ); scm.isBranchModified.mockResolvedValueOnce(false); expect( - await isOnboardingBranchModified('configure/renovate'), + await isOnboardingBranchModified('configure/renovate', 'main'), ).toBeFalse(); }); @@ -165,7 +165,9 @@ describe('workers/repository/onboarding/branch/onboarding-branch-cache', () => { 'new-onboarding-sha' as LongCommitSha, ); scm.isBranchModified.mockResolvedValueOnce(true); - expect(await isOnboardingBranchModified('configure/renovate')).toBeTrue(); + expect( + await isOnboardingBranchModified('configure/renovate', 'main'), + ).toBeTrue(); }); it('returns cached value', async () => { @@ -181,7 +183,9 @@ describe('workers/repository/onboarding/branch/onboarding-branch-cache', () => { git.getBranchCommit.mockReturnValueOnce( 'onboarding-sha' as LongCommitSha, ); - expect(await isOnboardingBranchModified('configure/renovate')).toBeTrue(); + expect( + await isOnboardingBranchModified('configure/renovate', 'main'), + ).toBeTrue(); }); }); diff --git a/lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts index d3ca81028b7d28..f28c08b0898ccf 100644 --- a/lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts +++ b/lib/workers/repository/onboarding/branch/onboarding-branch-cache.ts @@ -61,6 +61,7 @@ export function hasOnboardingBranchChanged(onboardingBranch: string): boolean { // once set to true it stays true as we do not rebase onboarding branches anymore (this feature will be added in future though) export async function isOnboardingBranchModified( onboardingBranch: string, + defaultBranch: string, ): Promise { const cache = getCache(); const onboardingCache = cache.onboardingBranchCache; @@ -74,7 +75,7 @@ export async function isOnboardingBranchModified( ) { return onboardingCache.isModified; } else { - isModified = await scm.isBranchModified(onboardingBranch); + isModified = await scm.isBranchModified(onboardingBranch, defaultBranch); } return isModified; diff --git a/lib/workers/repository/onboarding/branch/rebase.ts b/lib/workers/repository/onboarding/branch/rebase.ts index 14bd12b7c0fab8..247bf3844f76f0 100644 --- a/lib/workers/repository/onboarding/branch/rebase.ts +++ b/lib/workers/repository/onboarding/branch/rebase.ts @@ -58,6 +58,6 @@ export async function rebaseOnboardingBranch( }, ], message: commitMessage.toString(), - platformCommit: !!config.platformCommit, + platformCommit: config.platformCommit, }); } diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index a2aacf36f70911..3c67849975f522 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -4071,7 +4071,7 @@ describe('workers/repository/process/lookup/index', () => { config.datasource = NpmDatasource.id; config.packageRules = [ { - matchSourceUrlPrefixes: ['https://github.com/kriskowal/q'], + matchSourceUrls: ['https://github.com/kriskowal/**'], allowedVersions: '< 1.4.0', }, ]; diff --git a/lib/workers/repository/update/branch/commit.spec.ts b/lib/workers/repository/update/branch/commit.spec.ts index 6233b8b02c3306..1caf74d7380443 100644 --- a/lib/workers/repository/update/branch/commit.spec.ts +++ b/lib/workers/repository/update/branch/commit.spec.ts @@ -20,6 +20,7 @@ describe('workers/repository/update/branch/commit', () => { updatedPackageFiles: [], updatedArtifacts: [], upgrades: [], + platformCommit: 'auto', } satisfies BranchConfig; scm.commitAndPush.mockResolvedValueOnce('123test' as LongCommitSha); GlobalConfig.reset(); @@ -52,7 +53,7 @@ describe('workers/repository/update/branch/commit', () => { ], force: false, message: 'some commit message', - platformCommit: false, + platformCommit: 'auto', }, ], ]); diff --git a/lib/workers/repository/update/branch/commit.ts b/lib/workers/repository/update/branch/commit.ts index 44f317e9f34381..aa6c46d9a7be1a 100644 --- a/lib/workers/repository/update/branch/commit.ts +++ b/lib/workers/repository/update/branch/commit.ts @@ -59,6 +59,6 @@ export function commitFilesToBranch( files: updatedFiles, message: config.commitMessage!, force: !!config.forceCommit, - platformCommit: !!config.platformCommit, + platformCommit: config.platformCommit, }); } diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index c241925cdb8cc4..5429e4b7cf3142 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -255,7 +255,10 @@ export async function processBranch( } logger.debug('Checking if PR has been edited'); - const branchIsModified = await scm.isBranchModified(config.branchName); + const branchIsModified = await scm.isBranchModified( + config.branchName, + config.baseBranch, + ); if (branchPr) { logger.debug('Found existing branch PR'); if (branchPr.state !== 'open') { diff --git a/lib/workers/repository/update/branch/reuse.ts b/lib/workers/repository/update/branch/reuse.ts index 90c79ae5d1f608..4fc52c4830ece5 100644 --- a/lib/workers/repository/update/branch/reuse.ts +++ b/lib/workers/repository/update/branch/reuse.ts @@ -53,7 +53,7 @@ export async function shouldReuseExistingBranch( if (await scm.isBranchBehindBase(branchName, baseBranch)) { logger.debug(`Branch is behind base branch and needs rebasing`); // We can rebase the branch only if no PR or PR can be rebased - if (await scm.isBranchModified(branchName)) { + if (await scm.isBranchModified(branchName, baseBranch)) { logger.debug('Cannot rebase branch as it has been modified'); result.reuseExistingBranch = true; result.isModified = true; @@ -74,7 +74,7 @@ export async function shouldReuseExistingBranch( if (result.isConflicted) { logger.debug('Branch is conflicted'); - if ((await scm.isBranchModified(branchName)) === false) { + if ((await scm.isBranchModified(branchName, baseBranch)) === false) { logger.debug(`Branch is not mergeable and needs rebasing`); if ( config.rebaseWhen === 'never' && diff --git a/lib/workers/repository/update/pr/automerge.ts b/lib/workers/repository/update/pr/automerge.ts index b26089677b5c4d..9441e07b21f5d9 100644 --- a/lib/workers/repository/update/pr/automerge.ts +++ b/lib/workers/repository/update/pr/automerge.ts @@ -33,6 +33,7 @@ export async function checkAutoMerge( logger.trace({ config }, 'checkAutoMerge'); const { branchName, + baseBranch, automergeType, automergeStrategy, pruneBranchAfterAutomerge, @@ -50,7 +51,7 @@ export async function checkAutoMerge( } const isConflicted = config.isConflicted ?? - (await scm.isBranchConflicted(config.baseBranch, config.branchName)); + (await scm.isBranchConflicted(baseBranch, branchName)); if (isConflicted) { logger.debug('PR is conflicted'); return { @@ -68,7 +69,7 @@ export async function checkAutoMerge( }; } const branchStatus = await resolveBranchStatus( - config.branchName, + branchName, !!config.internalChecksAsSuccess, config.ignoreTests, ); @@ -81,8 +82,7 @@ export async function checkAutoMerge( prAutomergeBlockReason: 'BranchNotGreen', }; } - // Check if it's been touched - if (await scm.isBranchModified(branchName)) { + if (await scm.isBranchModified(branchName, baseBranch)) { logger.debug('PR is ready for automerge but has been modified'); return { automerged: false, diff --git a/package.json b/package.json index 4d8d9866da57b9..23d1df2e623dab 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,6 @@ "azure-devops-node-api": "13.0.0", "bunyan": "1.8.15", "cacache": "18.0.3", - "cacheable-lookup": "5.0.4", "chalk": "4.1.2", "changelog-filename-regex": "2.0.1", "clean-git-ref": "2.0.1", @@ -213,7 +212,6 @@ "jsonata": "2.0.5", "jsonc-parser": "3.2.1", "klona": "2.0.6", - "lru-cache": "10.2.2", "luxon": "3.4.4", "markdown-it": "14.1.0", "markdown-table": "2.0.0", @@ -295,7 +293,7 @@ "@types/mdast": "3.0.15", "@types/moo": "0.5.9", "@types/ms": "0.7.34", - "@types/node": "18.19.33", + "@types/node": "20.12.8", "@types/parse-link-header": "2.0.3", "@types/semver": "7.5.8", "@types/semver-stable": "3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdd83195a87db6..575558ce74d128 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,9 +110,6 @@ importers: cacache: specifier: 18.0.3 version: 18.0.3 - cacheable-lookup: - specifier: 5.0.4 - version: 5.0.4 chalk: specifier: 4.1.2 version: 4.1.2 @@ -224,9 +221,6 @@ importers: klona: specifier: 2.0.6 version: 2.0.6 - lru-cache: - specifier: 10.2.2 - version: 10.2.2 luxon: specifier: 3.4.4 version: 3.4.4 @@ -461,8 +455,8 @@ importers: specifier: 0.7.34 version: 0.7.34 '@types/node': - specifier: 18.19.33 - version: 18.19.33 + specifier: 20.12.8 + version: 20.12.8 '@types/parse-link-header': specifier: 2.0.3 version: 2.0.3 @@ -531,7 +525,7 @@ importers: version: 2.29.1(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jest: specifier: 27.9.0 - version: 27.9.0(@typescript-eslint/eslint-plugin@7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5) + version: 27.9.0(@typescript-eslint/eslint-plugin@7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5) eslint-plugin-jest-formatting: specifier: 3.1.0 version: 3.1.0(eslint@8.57.0) @@ -555,16 +549,16 @@ importers: version: 9.0.11 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + version: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) jest-extended: specifier: 4.0.2 - version: 4.0.2(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5))) + version: 4.0.2(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5))) jest-mock: specifier: 29.7.0 version: 29.7.0 jest-mock-extended: specifier: 3.0.7 - version: 3.0.7(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5) + version: 3.0.7(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5) jest-snapshot: specifier: 29.7.0 version: 29.7.0 @@ -600,10 +594,10 @@ importers: version: 3.0.3 ts-jest: specifier: 29.1.4 - version: 29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5) + version: 29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5) ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5) + version: 10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5) type-fest: specifier: 4.18.3 version: 4.18.3 @@ -1184,7 +1178,6 @@ packages: '@ls-lint/ls-lint@2.2.3': resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==} - cpu: [x64, arm64, s390x] os: [darwin, linux, win32] hasBin: true @@ -1295,6 +1288,10 @@ packages: typescript: optional: true + '@opentelemetry/api-logs@0.51.0': + resolution: {integrity: sha512-m/jtfBPEIXS1asltl8fPQtO3Sb1qMpuL61unQajUmM8zIxeMF1AlqzWXM3QedcYgTTFiJCew5uJjyhpmqhc0+g==} + engines: {node: '>=14'} + '@opentelemetry/api-logs@0.51.1': resolution: {integrity: sha512-E3skn949Pk1z2XtXu/lxf6QAZpawuTM/IUEXcAzpiUkTd73Hmvw26FiN3cJuTmkpM5hZzHwkomVdtrh/n/zzwA==} engines: {node: '>=14'} @@ -2031,8 +2028,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@18.19.33': - resolution: {integrity: sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==} + '@types/node@20.12.8': + resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3632,7 +3629,6 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -4428,6 +4424,10 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + minipass@7.1.0: + resolution: {integrity: sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==} + engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -5134,12 +5134,10 @@ packages: rimraf@2.4.5: resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==} - deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@5.0.7: @@ -7065,27 +7063,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5))': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -7110,7 +7108,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 jest-mock: 29.7.0 '@jest/expect-utils@29.4.1': @@ -7132,7 +7130,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 18.19.33 + '@types/node': 20.12.8 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -7154,7 +7152,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 18.19.33 + '@types/node': 20.12.8 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -7224,7 +7222,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -7396,6 +7394,10 @@ snapshots: optionalDependencies: typescript: 5.4.5 + '@opentelemetry/api-logs@0.51.0': + dependencies: + '@opentelemetry/api': 1.8.0 + '@opentelemetry/api-logs@0.51.1': dependencies: '@opentelemetry/api': 1.8.0 @@ -7423,7 +7425,7 @@ snapshots: '@opentelemetry/instrumentation-bunyan@0.38.0(@opentelemetry/api@1.8.0)': dependencies: '@opentelemetry/api': 1.8.0 - '@opentelemetry/api-logs': 0.51.1 + '@opentelemetry/api-logs': 0.51.0 '@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0) '@types/bunyan': 1.8.9 transitivePeerDependencies: @@ -8181,7 +8183,7 @@ snapshots: '@types/aws4@1.11.6': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/babel__core@7.20.5': dependencies: @@ -8206,27 +8208,27 @@ snapshots: '@types/better-sqlite3@7.6.10': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/breejs__later@4.1.5': {} '@types/bunyan@1.8.11': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/bunyan@1.8.9': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/cacache@17.0.2': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/responselike': 1.0.3 '@types/callsite@1.0.34': {} @@ -8253,7 +8255,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/git-url-parse@9.0.3': {} @@ -8263,7 +8265,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/http-cache-semantics@4.0.4': {} @@ -8289,11 +8291,11 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/keyv@3.1.4': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/linkify-it@3.0.5': {} @@ -8312,7 +8314,7 @@ snapshots: '@types/marshal@0.5.3': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/mdast@3.0.15': dependencies: @@ -8328,7 +8330,7 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@18.19.33': + '@types/node@20.12.8': dependencies: undici-types: 5.26.5 @@ -8338,7 +8340,7 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 '@types/semver-stable@3.0.2': {} @@ -8358,7 +8360,7 @@ snapshots: '@types/tar@6.1.13': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 minipass: 4.2.8 '@types/tmp@0.2.6': {} @@ -8383,7 +8385,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 optional: true '@typescript-eslint/eslint-plugin@7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': @@ -8902,7 +8904,7 @@ snapshots: fs-minipass: 3.0.3 glob: 10.4.1 lru-cache: 10.2.2 - minipass: 7.1.2 + minipass: 7.1.0 minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -9118,13 +9120,13 @@ snapshots: optionalDependencies: typescript: 5.4.5 - create-jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -9554,13 +9556,13 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) transitivePeerDependencies: - supports-color - typescript @@ -9843,7 +9845,7 @@ snapshots: fs-minipass@3.0.3: dependencies: - minipass: 7.1.2 + minipass: 7.1.0 fs.realpath@1.0.0: {} @@ -10489,7 +10491,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -10509,16 +10511,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -10528,7 +10530,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)): dependencies: '@babel/core': 7.24.6 '@jest/test-sequencer': 29.7.0 @@ -10553,8 +10555,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 18.19.33 - ts-node: 10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5) + '@types/node': 20.12.8 + ts-node: 10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -10583,16 +10585,16 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 jest-mock: 29.7.0 jest-util: 29.7.0 - jest-extended@4.0.2(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5))): + jest-extended@4.0.2(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5))): dependencies: jest-diff: 29.7.0 jest-get-type: 29.6.3 optionalDependencies: - jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) jest-get-type@29.6.3: {} @@ -10600,7 +10602,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 18.19.33 + '@types/node': 20.12.8 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -10643,16 +10645,16 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 - jest-mock-extended@3.0.7(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5): + jest-mock-extended@3.0.7(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5): dependencies: - jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) ts-essentials: 10.0.0(typescript@5.4.5) typescript: 5.4.5 jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -10687,7 +10689,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -10715,7 +10717,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 chalk: 4.1.2 cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 @@ -10761,7 +10763,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -10780,7 +10782,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.19.33 + '@types/node': 20.12.8 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -10789,17 +10791,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 18.19.33 + '@types/node': 20.12.8 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)): + jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -10992,7 +10994,7 @@ snapshots: cacache: 18.0.3 http-cache-semantics: 4.1.1 is-lambda: 1.0.1 - minipass: 7.1.2 + minipass: 7.1.0 minipass-fetch: 3.0.5 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -11189,11 +11191,11 @@ snapshots: minipass-collect@2.0.1: dependencies: - minipass: 7.1.2 + minipass: 7.1.0 minipass-fetch@3.0.5: dependencies: - minipass: 7.1.2 + minipass: 7.1.0 minipass-sized: 1.0.3 minizlib: 2.1.2 optionalDependencies: @@ -11221,6 +11223,8 @@ snapshots: minipass@5.0.0: {} + minipass@7.1.0: {} + minipass@7.1.2: {} minizlib@2.1.2: @@ -12199,7 +12203,7 @@ snapshots: ssri@10.0.6: dependencies: - minipass: 7.1.2 + minipass: 7.1.0 stack-utils@2.0.6: dependencies: @@ -12426,11 +12430,11 @@ snapshots: optionalDependencies: typescript: 5.4.5 - ts-jest@29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)))(typescript@5.4.5): + ts-jest@29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)))(typescript@5.4.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5)) + jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -12444,14 +12448,14 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.24.6) - ts-node@10.9.2(@swc/core@1.5.25)(@types/node@18.19.33)(typescript@5.4.5): + ts-node@10.9.2(@swc/core@1.5.25)(@types/node@20.12.8)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 18.19.33 + '@types/node': 20.12.8 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 diff --git a/test/documentation.spec.ts b/test/documentation.spec.ts index 6d1d54ce339926..d6ee275f90f7f3 100644 --- a/test/documentation.spec.ts +++ b/test/documentation.spec.ts @@ -1,6 +1,7 @@ import fs from 'fs-extra'; import { glob } from 'glob'; import { getOptions } from '../lib/config/options'; +import { allowedExperimentalFlags } from '../lib/constants/experimental-flags'; import { regEx } from '../lib/util/regex'; const options = getOptions(); @@ -115,5 +116,35 @@ describe('documentation', () => { ); }); }); + + describe('docs/usage/self-hosted-experimental-flags.md', () => { + function getSelfHostedExperimentalFlagsHeaders(file: string): string[] { + const content = fs.readFileSync(`docs/usage/${file}`, 'utf8'); + const matches = content.match(/\n## (.*?)\n/g) ?? []; + return matches.map((match) => + match.substring(4, match.length - 1).replaceAll('`', ''), + ); + } + + it('has headers sorted alphabetically', () => { + expect( + getSelfHostedExperimentalFlagsHeaders( + 'self-hosted-experimental-flags.md', + ), + ).toEqual( + getSelfHostedExperimentalFlagsHeaders( + 'self-hosted-experimental-flags.md', + ).sort(), + ); + }); + + it('all experimental flags are documented', () => { + expect( + getSelfHostedExperimentalFlagsHeaders( + 'self-hosted-experimental-flags.md', + ).sort(), + ).toEqual(Array.from(allowedExperimentalFlags).sort()); + }); + }); }); }); diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index bbcae0f9320b9b..42d8721efb6ad1 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -60,18 +60,10 @@ ARG RENOVATE_VERSION COPY --from=build --chown=root:root /usr/local/renovate/ /usr/local/renovate/ -# Compabillity, so `config.js` can access renovate and deps -RUN set -ex; \ - mkdir /opt/containerbase/tools/renovate; \ - echo "${RENOVATE_VERSION}" > /opt/containerbase/versions/renovate; \ - ln -sf /usr/local/renovate /opt/containerbase/tools/renovate/${RENOVATE_VERSION}; \ - ln -sf /usr/local/renovate/node_modules ./node_modules; \ - true - RUN set -ex; \ renovate --version; \ renovate-config-validator; \ - node -e "new require('re2')('.*').exec('test')"; \ + cd /usr/local/renovate && node -e "new require('re2')('.*').exec('test')"; \ true LABEL \ diff --git a/tools/docker/bake.hcl b/tools/docker/bake.hcl index 8a93f1924e94a4..47a29fdb06a8ec 100644 --- a/tools/docker/bake.hcl +++ b/tools/docker/bake.hcl @@ -74,17 +74,6 @@ target "slim" { notequal("", RENOVATE_VERSION) ? "${FILE}/${FILE}:${RENOVATE_VERSION}": "", notequal("", RENOVATE_MAJOR_VERSION) ? "${FILE}/${FILE}:${RENOVATE_MAJOR_VERSION}": "", notequal("", RENOVATE_MAJOR_MINOR_VERSION) ? "${FILE}/${FILE}:${RENOVATE_MAJOR_MINOR_VERSION}": "", - - // legacy slim tags - // TODO: remove on next major - "ghcr.io/${OWNER}/${FILE}:slim", - "${FILE}/${FILE}:slim", - notequal("", RENOVATE_VERSION) ? "ghcr.io/${OWNER}/${FILE}:${RENOVATE_VERSION}-slim": "", - notequal("", RENOVATE_MAJOR_VERSION) ? "ghcr.io/${OWNER}/${FILE}:${RENOVATE_MAJOR_VERSION}-slim": "", - notequal("", RENOVATE_MAJOR_MINOR_VERSION) ? "ghcr.io/${OWNER}/${FILE}:${RENOVATE_MAJOR_MINOR_VERSION}-slim": "", - notequal("", RENOVATE_VERSION) ? "${FILE}/${FILE}:${RENOVATE_VERSION}-slim": "", - notequal("", RENOVATE_MAJOR_VERSION) ? "${FILE}/${FILE}:${RENOVATE_MAJOR_VERSION}-slim": "", - notequal("", RENOVATE_MAJOR_MINOR_VERSION) ? "${FILE}/${FILE}:${RENOVATE_MAJOR_MINOR_VERSION}-slim": "", ] }