Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(managers/custom): generic manager for json files #32784

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
cb0dd89
implement jsonata manager
RahulGautamSingh Nov 26, 2024
6caecbd
validation
RahulGautamSingh Nov 28, 2024
739b28f
Merge branch 'main' into feat/generic-manager
RahulGautamSingh Nov 28, 2024
0432ece
fix tests
RahulGautamSingh Nov 28, 2024
8f88d8d
Merge branch 'feat/generic-manager' of https://github.com/RahulGautam…
RahulGautamSingh Nov 28, 2024
754324d
update docs
RahulGautamSingh Nov 28, 2024
c497553
fix tests
RahulGautamSingh Nov 28, 2024
f8a32ea
fix lint issue
RahulGautamSingh Nov 28, 2024
a375c51
refactor
RahulGautamSingh Nov 28, 2024
0b54c6d
fix ci issues
RahulGautamSingh Nov 28, 2024
4c14ac6
remove duplicate code
RahulGautamSingh Nov 28, 2024
caf4513
add jsonata to customType allowed values
RahulGautamSingh Nov 28, 2024
55fc985
Apply Suggestions
RahulGautamSingh Nov 29, 2024
69f6745
refactor: remove unused types
RahulGautamSingh Nov 29, 2024
dabb627
docs: refactor
RahulGautamSingh Nov 29, 2024
7f60365
Update docs/usage/configuration-options.md
RahulGautamSingh Nov 29, 2024
bfd00e1
apply suggestions
RahulGautamSingh Nov 30, 2024
292c91f
docs: redo structure
RahulGautamSingh Nov 30, 2024
1324d83
apply suggestions
RahulGautamSingh Dec 5, 2024
ff13a21
Apply Suggestions
RahulGautamSingh Dec 7, 2024
c8db815
docs: remove redundant codeblock
RahulGautamSingh Dec 7, 2024
c92ad2b
refactor: tests
RahulGautamSingh Dec 7, 2024
28e64e5
fix: docs
RahulGautamSingh Dec 11, 2024
cb25557
fix: types
RahulGautamSingh Dec 11, 2024
d4cfb2d
feat: add new field fileFormat
RahulGautamSingh Dec 11, 2024
96c0024
refactor: simplify logic for handleMatching()
RahulGautamSingh Dec 11, 2024
1ef8764
refactor: apply DRY concept
RahulGautamSingh Dec 11, 2024
ffd6228
fix(types): indentation
RahulGautamSingh Dec 11, 2024
c746d87
Merge branch 'main' into feat/generic-manager
RahulGautamSingh Dec 17, 2024
81d9121
Merge branch 'main' into feat/generic-manager
RahulGautamSingh Dec 18, 2024
4e4739c
validation for JSONata manager
RahulGautamSingh Dec 18, 2024
e66b916
docs(customManagers): fileFormat
RahulGautamSingh Dec 18, 2024
e28ba81
Apply Suggestion
RahulGautamSingh Dec 18, 2024
76b0cc4
fix issues
RahulGautamSingh Dec 18, 2024
0e48b18
apply suggestions
RahulGautamSingh Dec 18, 2024
f4ec390
Apply Suggestions
RahulGautamSingh Dec 19, 2024
66f2577
fix test
RahulGautamSingh Dec 19, 2024
a632dc6
Merge branch 'main' into feat/generic-manager
RahulGautamSingh Dec 19, 2024
d1da658
rebase
RahulGautamSingh Dec 19, 2024
e9ab58a
matchStrings: update description
RahulGautamSingh Dec 19, 2024
a36550e
update docs
RahulGautamSingh Dec 19, 2024
3788661
fix test
RahulGautamSingh Dec 21, 2024
cd68238
Apply Suggestions
RahulGautamSingh Jan 19, 2025
211927d
fix tests
RahulGautamSingh Jan 19, 2025
6f4fa17
fix lint isue
RahulGautamSingh Jan 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 71 additions & 10 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,22 +706,25 @@ You can define custom managers to handle:
- Proprietary file formats or conventions
- Popular file formats not yet supported as a manager by Renovate

Currently we only have one custom manager.
The `regex` manager which is based on using Regular Expression named capture groups.
Renovate has two custom managers:

You must have a named capture group matching (e.g. `(?<depName>.*)`) _or_ configure its corresponding template (e.g. `depNameTemplate`) for these fields:
| Custom manager | Matching engine |
| -------------- | ---------------------------------------------- |
| `regex` | Regular Expression, with named capture groups. |
| `jsonata` | JSONata query. |

You must capture/extract the following three fields _or_ configure its corresponding template (e.g. `depNameTemplate`) for these fields:

- `datasource`
- `depName` and / or `packageName`
- `currentValue`
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved

Use named capture group matching _or_ set a corresponding template.
We recommend you use only _one_ of these methods, or you'll get confused.
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved

We recommend that you also tell Renovate what `versioning` to use.
We also recommend that you also tell Renovate what `versioning` to use.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
We also recommend that you also tell Renovate what `versioning` to use.
Also, we recommend you explicitly set which `versioning` Renovate should use.

If the `versioning` field is missing, then Renovate defaults to using `semver` versioning.
Comment on lines +724 to 725
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't fully true. the datasource can set a different versioning (most data sources doing it). we should suggest this only if the datasource default versioning isn't suitable

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying @viceice's comment:

this isn't fully true. the datasource can set a different versioning (most data sources doing it). we should suggest this only if the datasource default versioning isn't suitable

What do you think of this text?

Suggested change
If the `versioning` field is missing, then Renovate defaults to using `semver` versioning.
Renovate defaults to `semver` versioning if _both_ condition are met:
- The `versioning` field is missing in the custom manager config
- The Renovate datasource does _not_ set its own default versioning


For more details and examples about it, see our [documentation for the `regex` manager](modules/manager/regex/index.md).
For more details and examples regarding custom managers, see our documentation for the [`regex` manager](modules/manager/regex/index.md) and the [`JSONata` manager](modules/manager/jsonata/index.md).
For template fields, use the triple brace `{{{ }}}` notation to avoid Handlebars escaping any special characters.

<!-- prettier-ignore -->
Expand Down Expand Up @@ -763,6 +766,10 @@ This will lead to following update where `1.21-alpine` is the newest version of
image: my.new.registry/aRepository/andImage:1.21-alpine
```

<!-- prettier-ignore -->
!!! note
Can only be used with the custom regex manager.

### currentValueTemplate

If the `currentValue` for a dependency is not captured with a named group then it can be defined in config using this field.
Expand All @@ -786,9 +793,24 @@ Example:
}
```

```json title="Parsing a JSON file with a custom manager"
{
"customManagers": [
{
"customType": "jsonata",
"fileFormat": "json",
"fileMatch": ["file.json"],
"matchStrings": [
"packages.{ \"depName\": package, \"currentValue\": version }"
]
}
]
}
```

### datasourceTemplate

If the `datasource` for a dependency is not captured with a named group then it can be defined in config using this field.
If the `datasource` for a dependency is not captured with a named group, then it can be defined in config using this field.
It will be compiled using Handlebars and the regex `groups` result.

### depNameTemplate
Expand All @@ -803,23 +825,58 @@ It will be compiled using Handlebars and the regex `groups` result.

### extractVersionTemplate

If `extractVersion` cannot be captured with a named capture group in `matchString` then it can be defined manually using this field.
If `extractVersion` cannot be captured with a named capture group in `matchString`, then it can be defined manually using this field.
It will be compiled using Handlebars and the regex `groups` result.

### fileFormat

`fileFormat` specifies the syntax of the package file that's managed by the custom JSONata manager.
This setting helps the system correctly parse and interpret the configuration file's contents.

Only the `json` format is supported.

```json title="Parsing a JSON file with a custom manager"
{
"customManagers": [
{
"customType": "jsonata",
"fileFormat": "json",
"fileMatch": [".renovaterc"],
"matchStrings": [
"packages.{ \"depName\": package, \"currentValue\": version }"
]
}
]
}
```

### matchStrings

Each `matchStrings` must be a valid regular expression, optionally with named capture groups.
Each `matchStrings` must be one of the following:

1. A valid regular expression, which may optionally include named capture groups (if using `customType=regex`)
2. Or, a valid, escaped [JSONata](https://docs.jsonata.org/overview.html) query (if using `customType=json`)

Read the [`customType`](#customtype) docs, to learn more.

Example:

```json
```json title="matchStrings with a valid regular expression"
{
"matchStrings": [
"ENV .*?_VERSION=(?<currentValue>.*) # (?<datasource>.*?)/(?<depName>.*?)\\s"
]
}
```

```json title="matchStrings with a valid JSONata query"
{
"matchStrings": [
"packages.{ \"depName\": package, \"currentValue\": version }"
]
}
```

### matchStringsStrategy

`matchStringsStrategy` controls behavior when multiple `matchStrings` values are provided.
Expand All @@ -829,6 +886,10 @@ Three options are available:
- `recursive`
- `combination`

<!--prettier-ignore-->
!!! note
`matchStringsStrategy` can only be used in a custom regex manager config!

#### any

Each provided `matchString` will be matched individually to the content of the `packageFile`.
Expand Down
16 changes: 12 additions & 4 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2734,18 +2734,26 @@ const options: RenovateOptions[] = [
description:
'Custom manager to use. Valid only within a `customManagers` object.',
type: 'string',
allowedValues: ['regex'],
allowedValues: ['jsonata', 'regex'],
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
parents: ['customManagers'],
cli: false,
env: false,
},
{
name: 'matchStrings',
name: 'fileFormat',
description:
'Regex capture rule to use. Valid only within a `customManagers` object.',
'It specifies the syntax of the package file being managed by the custom JSONata manager.',
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
type: 'string',
allowedValues: ['json'],
parents: ['customManagers'],
cli: false,
env: false,
},
{
name: 'matchStrings',
description: 'Queries to use. Valid only within a `customManagers` object.',
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
type: 'array',
subType: 'string',
format: 'regex',
parents: ['customManagers'],
cli: false,
env: false,
Expand Down
74 changes: 63 additions & 11 deletions lib/config/validation-helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import is from '@sindresorhus/is';
import jsonata from 'jsonata';
import { logger } from '../../logger';
import type {
RegexManagerConfig,
RegexManagerTemplates,
} from '../../modules/manager/custom/regex/types';
import type { RegexManagerTemplates } from '../../modules/manager/custom/regex/types';
import type { CustomManager } from '../../modules/manager/custom/types';
import { regEx } from '../../util/regex';
import type { ValidationMessage } from '../types';

Expand Down Expand Up @@ -78,21 +77,20 @@ export function isFalseGlobal(
return false;
}

function hasField(
customManager: Partial<RegexManagerConfig>,
field: string,
): boolean {
function hasField(customManager: CustomManager, field: string): boolean {
const templateField = `${field}Template` as keyof RegexManagerTemplates;
const fieldStr =
customManager.customType === 'regex' ? `(?<${field}>` : field;
return !!(
customManager[templateField] ??
customManager.matchStrings?.some((matchString) =>
matchString.includes(`(?<${field}>`),
matchString.includes(fieldStr),
)
);
}

export function validateRegexManagerFields(
customManager: Partial<RegexManagerConfig>,
customManager: CustomManager,
currentPath: string,
errors: ValidationMessage[],
): void {
Expand All @@ -114,7 +112,8 @@ export function validateRegexManagerFields(
} else {
errors.push({
topic: 'Configuration Error',
message: `Each Custom Manager must contain a non-empty matchStrings array`,
message:
'Each Custom Manager `matchStrings` array must have at least one item.',
});
}

Expand All @@ -136,3 +135,56 @@ export function validateRegexManagerFields(
});
}
}

export function validateJSONataManagerFields(
customManager: CustomManager,
currentPath: string,
errors: ValidationMessage[],
): void {
if (!is.nonEmptyString(customManager.fileFormat)) {
errors.push({
topic: 'Configuration Error',
message: 'Each JSONata manager must contain a fileFormat field.',
});
}

if (is.nonEmptyArray(customManager.matchStrings)) {
for (const matchString of customManager.matchStrings) {
try {
jsonata(matchString);
} catch (err) {
logger.debug(
{ err },
'customManager.matchStrings JSONata query validation error',
);
errors.push({
topic: 'Configuration Error',
message: `Invalid JSONata query for ${currentPath}: \`${matchString}\``,
});
}
}
} else {
errors.push({
topic: 'Configuration Error',
message: `Each Custom Manager must contain a non-empty matchStrings array`,
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
});
}

const mandatoryFields = ['currentValue', 'datasource'];
for (const field of mandatoryFields) {
if (!hasField(customManager, field)) {
errors.push({
topic: 'Configuration Error',
message: `JSONata Managers must contain ${field}Template configuration or ${field} in the query `,
});
}
}

const nameFields = ['depName', 'packageName'];
if (!nameFields.some((field) => hasField(customManager, field))) {
errors.push({
topic: 'Configuration Error',
message: `JSONata Managers must contain depName or packageName in the query or their templates`,
});
}
}
Loading
Loading