renovate-mcp 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/data/datasources.generated.js +2 -2
- package/dist/data/managers.generated.js +3 -2
- package/dist/data/migrations.generated.js +2 -2
- package/dist/data/options.generated.js +17 -3
- package/dist/data/presets.generated.js +91 -4
- package/dist/index.js +5 -2
- package/dist/lib/configLinter.js +14 -15
- package/dist/lib/configLocations.js +40 -12
- package/dist/lib/customManagerPreview.js +2 -2
- package/dist/lib/dependencyExplainer.js +1 -1
- package/dist/lib/externalPresetFetcher.js +6 -1
- package/dist/lib/pathSelection.js +266 -0
- package/dist/lib/setupCheck.js +1 -0
- package/dist/lib/version.js +1 -1
- package/dist/prompts/workflows.js +2 -2
- package/dist/tools/explainPaths.js +58 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Then try the prompt: *"List the namespaces available under `renovate://presets`.
|
|
|
30
30
|
|
|
31
31
|
## Tools & resources
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Nineteen tools, seven resources, and four workflow prompts. Each tool name below links to its full reference in [`docs/tools.md`](docs/tools.md).
|
|
34
34
|
|
|
35
35
|
| Tool | Purpose |
|
|
36
36
|
| --- | --- |
|
|
@@ -50,6 +50,7 @@ Eighteen tools, seven resources, and four workflow prompts. Each tool name below
|
|
|
50
50
|
| [`annotate_dry_run`](docs/tools.md#annotate_dry_run) | Attribute each proposed update in a `dry_run` report to the `packageRules` that caused it; flags rules that never matched and report fields the matchers needed but the report lacked. Stateless + offline. |
|
|
51
51
|
| [`explain_dependency`](docs/tools.md#explain_dependency) | Answer "why was/wasn't dependency X updated?" from a `dry_run` report: per-occurrence `skipReason`, proposed `updates`, `warnings`, a one-line verdict and remedy hints; optionally the `packageRules` that matched it. Stateless + offline. |
|
|
52
52
|
| [`explain_schedule`](docs/tools.md#explain_schedule) | Answer "when will this `schedule` actually run?": active-now plus the next run windows in UTC and the config's `timezone`, computed with Renovate's own schedule matcher; invalid schedules return Renovate's message and the fact that it then runs at any time. Offline. |
|
|
53
|
+
| [`explain_paths`](docs/tools.md#explain_paths) | Show which files Renovate scans under `includePaths` / `ignorePaths` (preset-added ones included, each attributed to its source), flag dead patterns and `ignorePaths` entries that drop files only by substring (`test` dropping `src/latest/`), and count each `matchFileNames` entry against the real repo. Offline. |
|
|
53
54
|
| [`migrate_config`](docs/tools.md#migrate_config) | Apply Renovate's built-in migrations and return the migrated config plus a unified diff. Does not write. |
|
|
54
55
|
| [`write_config`](docs/tools.md#write_config) | Validate, then atomically write a config to disk. Preserves comments/key order on existing JSON-with-comments files; for `package.json` edits only the nested `renovate` key. |
|
|
55
56
|
| [`renovate://presets`](docs/tools.md#renovatepresets) (resource) | Markdown index of all built-in presets grouped by namespace. |
|
|
@@ -74,7 +75,7 @@ Eighteen tools, seven resources, and four workflow prompts. Each tool name below
|
|
|
74
75
|
- **Linux or macOS.** Windows is not supported — `package.json` declares `"os": ["darwin", "linux"]`, so `npm i` surfaces an `EBADPLATFORM` warning on Windows and the server exits with a clear stderr message at startup. Use WSL2 or a Linux/macOS host instead.
|
|
75
76
|
- **Node.js ≥ 24.11** (aligns with Renovate's own engine requirement).
|
|
76
77
|
|
|
77
|
-
Renovate ships bundled — the `renovate` package is a runtime dependency, so `validate_config`, `dry_run`, and `write_config` work out of the box with no separate install. The offline tools (`read_config`, `suggest_presets`, `resolve_config`, `explain_config`, `resolve_config_diff`, `test_package_rules`, `preview_custom_manager`, `lint_config`, `dry_run_diff`, `annotate_dry_run`, `explain_dependency`, `explain_schedule`, `migrate_config`) never spawn Renovate at all — nor do `check_setup` (by default it reads version metadata from the bundled install instead of spawning) or `get_version` (it only reports the renovate-mcp server's own version).
|
|
78
|
+
Renovate ships bundled — the `renovate` package is a runtime dependency, so `validate_config`, `dry_run`, and `write_config` work out of the box with no separate install. The offline tools (`read_config`, `suggest_presets`, `resolve_config`, `explain_config`, `resolve_config_diff`, `test_package_rules`, `preview_custom_manager`, `lint_config`, `dry_run_diff`, `annotate_dry_run`, `explain_dependency`, `explain_schedule`, `explain_paths`, `migrate_config`) never spawn Renovate at all — nor do `check_setup` (by default it reads version metadata from the bundled install instead of spawning) or `get_version` (it only reports the renovate-mcp server's own version).
|
|
78
79
|
|
|
79
80
|
**Optional env vars:**
|
|
80
81
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/generate-datasources.mjs from renovate@44.
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-datasources.mjs from renovate@44.198.3.
|
|
2
2
|
// Do not edit by hand. Run `npm run generate:datasources` to refresh.
|
|
3
|
-
export const RENOVATE_VERSION = "44.
|
|
3
|
+
export const RENOVATE_VERSION = "44.198.3";
|
|
4
4
|
/**
|
|
5
5
|
* All built-in datasource ids accepted by Renovate's `matchDatasources` and
|
|
6
6
|
* `customManagers[].datasourceTemplate`. Custom datasources (`custom.<name>`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/generate-managers.mjs from renovate@44.
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-managers.mjs from renovate@44.198.3.
|
|
2
2
|
// Do not edit by hand. Run `npm run generate:managers` to refresh.
|
|
3
|
-
export const RENOVATE_VERSION = "44.
|
|
3
|
+
export const RENOVATE_VERSION = "44.198.3";
|
|
4
4
|
/**
|
|
5
5
|
* All manager names accepted by Renovate's `matchManagers` /
|
|
6
6
|
* `excludeManagers` fields. Includes both regular managers and custom
|
|
@@ -88,6 +88,7 @@ export const ALL_MANAGERS = Object.freeze([
|
|
|
88
88
|
"nvm",
|
|
89
89
|
"ocb",
|
|
90
90
|
"osgi",
|
|
91
|
+
"paket",
|
|
91
92
|
"pep621",
|
|
92
93
|
"pep723",
|
|
93
94
|
"pip-compile",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/generate-migrations.mjs from renovate@44.
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-migrations.mjs from renovate@44.198.3.
|
|
2
2
|
// Do not edit by hand. Run `npm run generate:migrations` to refresh.
|
|
3
|
-
export const RENOVATE_VERSION = "44.
|
|
3
|
+
export const RENOVATE_VERSION = "44.198.3";
|
|
4
4
|
/**
|
|
5
5
|
* Deprecated config keys. Sourced from Renovate's `MigrationsService`:
|
|
6
6
|
* `renamedProperties` (plain renames, with `newKey`) plus every
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/generate-options.mjs from renovate@44.
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-options.mjs from renovate@44.198.3.
|
|
2
2
|
// Do not edit by hand. Run `npm run generate:options` to refresh.
|
|
3
|
-
export const RENOVATE_VERSION = "44.
|
|
3
|
+
export const RENOVATE_VERSION = "44.198.3";
|
|
4
4
|
export const OPTIONS = {
|
|
5
5
|
"abandonmentThreshold": {
|
|
6
6
|
"description": "Flags packages that have not been updated within this period as abandoned.",
|
|
@@ -643,6 +643,7 @@ export const OPTIONS = {
|
|
|
643
643
|
"major",
|
|
644
644
|
"minor",
|
|
645
645
|
"patch",
|
|
646
|
+
"prerelease",
|
|
646
647
|
"sync"
|
|
647
648
|
],
|
|
648
649
|
"parents": [
|
|
@@ -1403,7 +1404,7 @@ export const OPTIONS = {
|
|
|
1403
1404
|
"dockerSidecarImage": {
|
|
1404
1405
|
"description": "Change this value to override the default Renovate sidecar image.",
|
|
1405
1406
|
"type": "string",
|
|
1406
|
-
"default": "ghcr.io/renovatebot/base-image:13.
|
|
1407
|
+
"default": "ghcr.io/renovatebot/base-image:13.149.0",
|
|
1407
1408
|
"globalOnly": true,
|
|
1408
1409
|
"deprecationMsg": "The usage of `binarySource=docker` is deprecated, and will be removed in the future"
|
|
1409
1410
|
},
|
|
@@ -1545,6 +1546,7 @@ export const OPTIONS = {
|
|
|
1545
1546
|
"nvm",
|
|
1546
1547
|
"ocb",
|
|
1547
1548
|
"osgi",
|
|
1549
|
+
"paket",
|
|
1548
1550
|
"pep621",
|
|
1549
1551
|
"pep723",
|
|
1550
1552
|
"pip-compile",
|
|
@@ -3421,6 +3423,18 @@ export const OPTIONS = {
|
|
|
3421
3423
|
"type": "array",
|
|
3422
3424
|
"stage": "package"
|
|
3423
3425
|
},
|
|
3426
|
+
"paket": {
|
|
3427
|
+
"description": "Configuration object for the paket manager",
|
|
3428
|
+
"type": "object",
|
|
3429
|
+
"default": {
|
|
3430
|
+
"managerFilePatterns": [
|
|
3431
|
+
"/(^|/)paket\\.dependencies$/"
|
|
3432
|
+
],
|
|
3433
|
+
"versioning": "paket"
|
|
3434
|
+
},
|
|
3435
|
+
"stage": "package",
|
|
3436
|
+
"cli": false
|
|
3437
|
+
},
|
|
3424
3438
|
"password": {
|
|
3425
3439
|
"description": "Password for authentication.",
|
|
3426
3440
|
"type": "string",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/generate-presets.mjs from renovate@44.
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-presets.mjs from renovate@44.198.3.
|
|
2
2
|
// Do not edit by hand. Run `npm run generate:presets` to refresh.
|
|
3
|
-
export const RENOVATE_VERSION = "44.
|
|
3
|
+
export const RENOVATE_VERSION = "44.198.3";
|
|
4
4
|
export const PRESETS = {
|
|
5
5
|
"abandonments:recommended": {
|
|
6
6
|
"namespace": "abandonments",
|
|
@@ -6965,6 +6965,27 @@ export const PRESETS = {
|
|
|
6965
6965
|
]
|
|
6966
6966
|
}
|
|
6967
6967
|
},
|
|
6968
|
+
"group:logtapeMonorepo": {
|
|
6969
|
+
"namespace": "group",
|
|
6970
|
+
"description": null,
|
|
6971
|
+
"body": {
|
|
6972
|
+
"packageRules": [
|
|
6973
|
+
{
|
|
6974
|
+
"description": "Group packages from logtape monorepo together.",
|
|
6975
|
+
"extends": [
|
|
6976
|
+
"monorepo:logtape"
|
|
6977
|
+
],
|
|
6978
|
+
"groupName": "logtape monorepo",
|
|
6979
|
+
"matchUpdateTypes": [
|
|
6980
|
+
"digest",
|
|
6981
|
+
"patch",
|
|
6982
|
+
"minor",
|
|
6983
|
+
"major"
|
|
6984
|
+
]
|
|
6985
|
+
}
|
|
6986
|
+
]
|
|
6987
|
+
}
|
|
6988
|
+
},
|
|
6968
6989
|
"group:loopbackMonorepo": {
|
|
6969
6990
|
"namespace": "group",
|
|
6970
6991
|
"description": null,
|
|
@@ -7587,6 +7608,7 @@ export const PRESETS = {
|
|
|
7587
7608
|
"group:linkifyjsMonorepo",
|
|
7588
7609
|
"group:log4j2Monorepo",
|
|
7589
7610
|
"group:logbackMonorepo",
|
|
7611
|
+
"group:logtapeMonorepo",
|
|
7590
7612
|
"group:loopbackMonorepo",
|
|
7591
7613
|
"group:lrnwebcomponentsMonorepo",
|
|
7592
7614
|
"group:lucideMonorepo",
|
|
@@ -15784,6 +15806,15 @@ export const PRESETS = {
|
|
|
15784
15806
|
]
|
|
15785
15807
|
}
|
|
15786
15808
|
},
|
|
15809
|
+
"monorepo:logtape": {
|
|
15810
|
+
"namespace": "monorepo",
|
|
15811
|
+
"description": "logtape monorepo",
|
|
15812
|
+
"body": {
|
|
15813
|
+
"matchSourceUrls": [
|
|
15814
|
+
"https://github.com/dahlia/logtape"
|
|
15815
|
+
]
|
|
15816
|
+
}
|
|
15817
|
+
},
|
|
15787
15818
|
"monorepo:loopback": {
|
|
15788
15819
|
"namespace": "monorepo",
|
|
15789
15820
|
"description": "loopback monorepo",
|
|
@@ -16525,7 +16556,9 @@ export const PRESETS = {
|
|
|
16525
16556
|
"description": "prisma monorepo",
|
|
16526
16557
|
"body": {
|
|
16527
16558
|
"matchSourceUrls": [
|
|
16528
|
-
"https://github.com/prisma/
|
|
16559
|
+
"https://github.com/prisma/orm",
|
|
16560
|
+
"https://github.com/prisma/prisma",
|
|
16561
|
+
"https://github.com/prisma/prisma-cli"
|
|
16529
16562
|
]
|
|
16530
16563
|
}
|
|
16531
16564
|
},
|
|
@@ -18042,7 +18075,9 @@ export const PRESETS = {
|
|
|
18042
18075
|
"packages:tslint"
|
|
18043
18076
|
],
|
|
18044
18077
|
"matchPackageNames": [
|
|
18078
|
+
"@biomejs/**",
|
|
18045
18079
|
"@oxlint/**",
|
|
18080
|
+
"oxfmt",
|
|
18046
18081
|
"oxlint",
|
|
18047
18082
|
"prettier",
|
|
18048
18083
|
"remark-lint",
|
|
@@ -18290,6 +18325,7 @@ export const PRESETS = {
|
|
|
18290
18325
|
"replacements:mem-rename",
|
|
18291
18326
|
"replacements:messageFormat-to-scoped",
|
|
18292
18327
|
"replacements:middie-to-scoped",
|
|
18328
|
+
"replacements:monochromata-cucumber-reporting-plugin-to-jvt",
|
|
18293
18329
|
"replacements:now-to-vercel",
|
|
18294
18330
|
"replacements:npm-run-all-to-maintenance-fork",
|
|
18295
18331
|
"replacements:ojdbc-to-ojdbc11",
|
|
@@ -19835,6 +19871,24 @@ export const PRESETS = {
|
|
|
19835
19871
|
]
|
|
19836
19872
|
}
|
|
19837
19873
|
},
|
|
19874
|
+
"replacements:monochromata-cucumber-reporting-plugin-to-jvt": {
|
|
19875
|
+
"namespace": "replacements",
|
|
19876
|
+
"description": "`de.monochromata.cucumber:reporting-plugin` was moved to `me.jvt.cucumber:reporting-plugin`.",
|
|
19877
|
+
"body": {
|
|
19878
|
+
"packageRules": [
|
|
19879
|
+
{
|
|
19880
|
+
"matchDatasources": [
|
|
19881
|
+
"maven"
|
|
19882
|
+
],
|
|
19883
|
+
"matchPackageNames": [
|
|
19884
|
+
"de.monochromata.cucumber:reporting-plugin"
|
|
19885
|
+
],
|
|
19886
|
+
"replacementName": "me.jvt.cucumber:reporting-plugin",
|
|
19887
|
+
"replacementVersion": "5.0.0"
|
|
19888
|
+
}
|
|
19889
|
+
]
|
|
19890
|
+
}
|
|
19891
|
+
},
|
|
19838
19892
|
"replacements:now-to-vercel": {
|
|
19839
19893
|
"namespace": "replacements",
|
|
19840
19894
|
"description": "`now` was renamed to `vercel`.",
|
|
@@ -21203,7 +21257,8 @@ export const PRESETS = {
|
|
|
21203
21257
|
"workarounds:libericaJdkDockerVersioning",
|
|
21204
21258
|
"workarounds:ubuntuDockerVersioning",
|
|
21205
21259
|
"workarounds:groupings",
|
|
21206
|
-
"workarounds:helmDocs"
|
|
21260
|
+
"workarounds:helmDocs",
|
|
21261
|
+
"workarounds:guavaVariants"
|
|
21207
21262
|
],
|
|
21208
21263
|
"overrideDescription": [
|
|
21209
21264
|
"Apply crowd-sourced workarounds for known problems with packages."
|
|
@@ -21384,6 +21439,38 @@ export const PRESETS = {
|
|
|
21384
21439
|
"matchUpdateTypes": [
|
|
21385
21440
|
"major"
|
|
21386
21441
|
]
|
|
21442
|
+
},
|
|
21443
|
+
{
|
|
21444
|
+
"description": "Group SimpleWebAuthn major updates to keep browser and server compatible.",
|
|
21445
|
+
"groupName": "SimpleWebAuthn",
|
|
21446
|
+
"matchDatasources": [
|
|
21447
|
+
"npm"
|
|
21448
|
+
],
|
|
21449
|
+
"matchPackageNames": [
|
|
21450
|
+
"@simplewebauthn/browser",
|
|
21451
|
+
"@simplewebauthn/server"
|
|
21452
|
+
],
|
|
21453
|
+
"matchUpdateTypes": [
|
|
21454
|
+
"major"
|
|
21455
|
+
]
|
|
21456
|
+
}
|
|
21457
|
+
]
|
|
21458
|
+
}
|
|
21459
|
+
},
|
|
21460
|
+
"workarounds:guavaVariants": {
|
|
21461
|
+
"namespace": "workarounds",
|
|
21462
|
+
"description": "Keep Guava on its `-jre` or `-android` variant.",
|
|
21463
|
+
"body": {
|
|
21464
|
+
"packageRules": [
|
|
21465
|
+
{
|
|
21466
|
+
"matchDatasources": [
|
|
21467
|
+
"maven",
|
|
21468
|
+
"sbt-package"
|
|
21469
|
+
],
|
|
21470
|
+
"matchPackageNames": [
|
|
21471
|
+
"com.google.guava:guava"
|
|
21472
|
+
],
|
|
21473
|
+
"versionCompatibility": "^(?<version>.+)-(?<compatibility>jre|android)$"
|
|
21387
21474
|
}
|
|
21388
21475
|
]
|
|
21389
21476
|
}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { registerTestPackageRules } from "./tools/testPackageRules.js";
|
|
|
16
16
|
import { registerAnnotateDryRun } from "./tools/annotateDryRun.js";
|
|
17
17
|
import { registerExplainDependency } from "./tools/explainDependency.js";
|
|
18
18
|
import { registerExplainSchedule } from "./tools/explainSchedule.js";
|
|
19
|
+
import { registerExplainPaths } from "./tools/explainPaths.js";
|
|
19
20
|
import { registerWriteConfig } from "./tools/writeConfig.js";
|
|
20
21
|
import { registerMigrateConfig } from "./tools/migrateConfig.js";
|
|
21
22
|
import { registerGetVersion } from "./tools/getVersion.js";
|
|
@@ -52,8 +53,9 @@ const BASE_INSTRUCTIONS = [
|
|
|
52
53
|
" 12. annotate_dry_run — attribute each proposed update in a dry_run report to the packageRules that caused it (and flag rules that never matched)",
|
|
53
54
|
" 13. explain_dependency — why was/wasn't a given dependency updated: skipReason, updates, warnings and matched rules, read from a dry_run report (offline)",
|
|
54
55
|
" 14. explain_schedule — when will a `schedule` actually run: active-now plus the next windows in UTC and the config's timezone, or Renovate's own invalid-schedule message (offline)",
|
|
55
|
-
" 15.
|
|
56
|
-
" 16.
|
|
56
|
+
" 15. explain_paths — which files Renovate scans under includePaths / ignorePaths (presets included), with dead patterns, substring-only ignorePaths surprises and per-rule matchFileNames counts (offline)",
|
|
57
|
+
" 16. migrate_config — apply Renovate's built-in migrations (deprecated keys → current schema) and return the migrated config",
|
|
58
|
+
" 17. write_config — save the agreed-upon config (validates first)",
|
|
57
59
|
"",
|
|
58
60
|
"Before the first repo-touching tool call in a session (read_config, resolve_config, dry_run, write_config, …), call check_setup with the same repoPath. It surfaces token / endpoint / connectivity problems up front (e.g. \"set GITHUB_TOKEN or github-actions deps will be skipped\") instead of waiting for dry_run to fail. Skip if the user has already confirmed setup or is doing offline-only work that doesn't depend on git origin or registries.",
|
|
59
61
|
"If any tool fails unexpectedly, call check_setup to diagnose CLI availability.",
|
|
@@ -87,6 +89,7 @@ registerTestPackageRules(server);
|
|
|
87
89
|
registerAnnotateDryRun(server);
|
|
88
90
|
registerExplainDependency(server);
|
|
89
91
|
registerExplainSchedule(server);
|
|
92
|
+
registerExplainPaths(server);
|
|
90
93
|
registerMigrateConfig(server);
|
|
91
94
|
registerWriteConfig(server);
|
|
92
95
|
registerGetVersion(server);
|
package/dist/lib/configLinter.js
CHANGED
|
@@ -338,23 +338,22 @@ function checkAutomergeIncludesMajor(config, findings) {
|
|
|
338
338
|
const pkgRules = config.packageRules;
|
|
339
339
|
if (!Array.isArray(pkgRules))
|
|
340
340
|
return;
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
// guard
|
|
344
|
-
//
|
|
345
|
-
//
|
|
341
|
+
// Renovate applies packageRules in order and a later match overrides an
|
|
342
|
+
// earlier one, so only a `matchUpdateTypes` including "major" + `automerge:
|
|
343
|
+
// false` guard AFTER an offending entry protects it; an earlier guard is
|
|
344
|
+
// itself overridden.
|
|
345
|
+
// ponytail: an entry counts as guarded by any later guard, without checking
|
|
346
|
+
// that the guard's selectors actually cover the same deps as the offending
|
|
347
|
+
// entry. Ceiling: a guard scoped to a different dep subset silently
|
|
348
|
+
// suppresses findings for unrelated earlier entries. Upgrade path: track
|
|
346
349
|
// selector overlap per-entry if false negatives show up in practice.
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return false;
|
|
352
|
-
return (Array.isArray(entry.matchUpdateTypes) &&
|
|
353
|
-
entry.matchUpdateTypes.includes("major"));
|
|
354
|
-
});
|
|
355
|
-
if (hasLaterMajorGuard)
|
|
356
|
-
return;
|
|
350
|
+
const lastGuardIndex = pkgRules.findLastIndex((entry) => isRecord(entry) &&
|
|
351
|
+
entry.automerge === false &&
|
|
352
|
+
Array.isArray(entry.matchUpdateTypes) &&
|
|
353
|
+
entry.matchUpdateTypes.includes("major"));
|
|
357
354
|
pkgRules.forEach((entry, i) => {
|
|
355
|
+
if (i < lastGuardIndex)
|
|
356
|
+
return;
|
|
358
357
|
if (!isRecord(entry))
|
|
359
358
|
return;
|
|
360
359
|
if (entry.automerge !== true)
|
|
@@ -35,7 +35,11 @@ export async function locateConfig(repoPath) {
|
|
|
35
35
|
// Renovate parses every candidate JSONC-then-JSON5 regardless of
|
|
36
36
|
// extension (and write_config round-trips comments into renovate.json);
|
|
37
37
|
// JSON5 is a superset of JSONC, so one parser covers all of them.
|
|
38
|
-
|
|
38
|
+
// A 0-length file reads as `{}`, as in Renovate's detectRepoFileConfig
|
|
39
|
+
// (`if (!configFileRaw.length) configFileRaw = "{}"`). Length only: a
|
|
40
|
+
// whitespace-only file still throws, as in Renovate. This intentionally
|
|
41
|
+
// differs from configWriter.ts, which treats whitespace-only as absent.
|
|
42
|
+
const config = raw.length === 0 ? {} : JSON5.parse(raw);
|
|
39
43
|
if (!isRecord(config)) {
|
|
40
44
|
throw new Error(`${candidate.path} does not contain a JSON object`);
|
|
41
45
|
}
|
|
@@ -55,17 +59,41 @@ export async function locateConfig(repoPath) {
|
|
|
55
59
|
const pkgAbs = path.join(repoPath, "package.json");
|
|
56
60
|
try {
|
|
57
61
|
const raw = await fs.readFile(pkgAbs, "utf8");
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
let pkg;
|
|
63
|
+
try {
|
|
64
|
+
pkg = JSON.parse(raw);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
// Invalid JSON means no config, mirroring Renovate's `catch {}`. Other
|
|
68
|
+
// read errors (EACCES etc.) still throw: a deliberate divergence.
|
|
69
|
+
if (err instanceof SyntaxError)
|
|
70
|
+
return null;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
if (isRecord(pkg) && "renovate" in pkg) {
|
|
74
|
+
const value = pkg.renovate;
|
|
75
|
+
if (isRecord(value)) {
|
|
76
|
+
return {
|
|
77
|
+
absPath: pkgAbs,
|
|
78
|
+
relPath: "package.json",
|
|
79
|
+
format: "package.json",
|
|
80
|
+
raw,
|
|
81
|
+
config: value,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Renovate expands a string package.json#renovate to `extends`.
|
|
85
|
+
if (typeof value === "string" && value.length > 0) {
|
|
86
|
+
return {
|
|
87
|
+
absPath: pkgAbs,
|
|
88
|
+
relPath: "package.json",
|
|
89
|
+
format: "package.json",
|
|
90
|
+
raw,
|
|
91
|
+
config: { extends: [value] },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// An empty string or other non-object value (true, a number, an array)
|
|
95
|
+
// is reported as no config. Deliberate divergence for these truthy
|
|
96
|
+
// values: Renovate selects the file but ends up with no usable settings.
|
|
69
97
|
}
|
|
70
98
|
}
|
|
71
99
|
catch (err) {
|
|
@@ -33,7 +33,7 @@ export async function parseStructured(content, fileFormat) {
|
|
|
33
33
|
const DEFAULT_MAX_FILES_WALKED = 2000;
|
|
34
34
|
const DEFAULT_MAX_FILES_MATCHED = 500;
|
|
35
35
|
const DEFAULT_MAX_HITS_PER_FILE = 100;
|
|
36
|
-
const DEFAULT_MATCH_TIMEOUT_MS = 2000;
|
|
36
|
+
export const DEFAULT_MATCH_TIMEOUT_MS = 2000;
|
|
37
37
|
const DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
38
38
|
const SKIP_DIRS = new Set([".git", "node_modules"]);
|
|
39
39
|
const TEMPLATE_FIELD_MAP = [
|
|
@@ -114,7 +114,7 @@ function parseRegexPattern(pattern) {
|
|
|
114
114
|
* therefore ADDS its complement rather than excluding — Renovate's
|
|
115
115
|
* `ignorePaths` is the exclusion mechanism. Walk order is preserved.
|
|
116
116
|
*/
|
|
117
|
-
async function matchFilePatterns(allPaths, patterns, matchTimeoutMs, warnings) {
|
|
117
|
+
export async function matchFilePatterns(allPaths, patterns, matchTimeoutMs, warnings) {
|
|
118
118
|
const matched = new Set();
|
|
119
119
|
for (let i = 0; i < patterns.length; i++) {
|
|
120
120
|
const pattern = patterns[i];
|
|
@@ -25,7 +25,7 @@ export const SKIP_HINTS = {
|
|
|
25
25
|
const NOT_FOUND_HINTS = [
|
|
26
26
|
"Check `enabledManagers`: the manager that owns this dependency's file may not be enabled.",
|
|
27
27
|
"Check the manager's `fileMatch` / `managerFilePatterns`: the file holding this dependency may not match the pattern.",
|
|
28
|
-
"Check `ignorePaths`: the file may live under an ignored path (defaults include `**/node_modules/**`, `**/bower_components/**`, …).",
|
|
28
|
+
"Check `ignorePaths`: the file may live under an ignored path (defaults include `**/node_modules/**`, `**/bower_components/**`, …). Run explain_paths with repoPath to see which pattern drops it.",
|
|
29
29
|
'Re-run dry_run with `dryRunMode: "extract"` and search the report to see which files and dependencies Renovate extracted at all.',
|
|
30
30
|
];
|
|
31
31
|
const NO_LOOKUP_HINT = 'A hit carries no `updates` array, so no lookup ran — the report is likely from `dryRunMode: "extract"`. Re-run dry_run with `dryRunMode: "lookup"` (or `"full"`) to see whether updates exist.';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import JSON5 from "json5";
|
|
1
2
|
import { resolveCredential } from "./credentialResolver.js";
|
|
2
3
|
import { validateEndpoint } from "./endpointValidator.js";
|
|
3
4
|
import { classifyExternalSource, } from "./presetResolver.js";
|
|
@@ -219,7 +220,11 @@ async function fetchJson(url, headers, timeoutMs, presetName, fetchImpl, platfor
|
|
|
219
220
|
};
|
|
220
221
|
}
|
|
221
222
|
try {
|
|
222
|
-
|
|
223
|
+
// Renovate parses preset files as JSONC, falling back to JSON5 (`.json5`
|
|
224
|
+
// straight to JSON5); JSON5 is a superset of JSONC, so one parser covers
|
|
225
|
+
// all of them. It also accepts JSON5 syntax in a `.jsonc` file, which
|
|
226
|
+
// Renovate rejects — matching that would need jsonc-parser here.
|
|
227
|
+
const body = JSON5.parse(text);
|
|
223
228
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
224
229
|
return {
|
|
225
230
|
ok: false,
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isDeepStrictEqual, promisify } from "node:util";
|
|
5
|
+
import { Minimatch } from "minimatch";
|
|
6
|
+
import { OPTIONS } from "../data/options.generated.js";
|
|
7
|
+
import { explainConfig } from "./configExplainer.js";
|
|
8
|
+
import { DEFAULT_MATCH_TIMEOUT_MS, matchFilePatterns } from "./customManagerPreview.js";
|
|
9
|
+
import { isRecord } from "./util.js";
|
|
10
|
+
const execFileP = promisify(execFile);
|
|
11
|
+
export const DEFAULT_MAX_FILES = 50_000;
|
|
12
|
+
const SAMPLE_SIZE = 5;
|
|
13
|
+
const MAX_SUBSTRING_HITS = 50;
|
|
14
|
+
const RENOVATE_DEFAULT = "<renovate default>";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Renovate's own filters, mirrored from
|
|
17
|
+
// node_modules/renovate/dist/workers/repository/extract/file-match.js
|
|
18
|
+
// (`getIncludedFiles` / `filterIgnoredFiles`). Mirrored rather than
|
|
19
|
+
// worker-imported because they are two one-liners over minimatch (already a
|
|
20
|
+
// direct dep pinned to Renovate's major); test/unit/pathSelection.test.ts runs
|
|
21
|
+
// the real functions as an oracle so the mirror can't drift silently.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const mmCache = new Map();
|
|
24
|
+
function glob(pattern) {
|
|
25
|
+
let mm = mmCache.get(pattern);
|
|
26
|
+
if (!mm)
|
|
27
|
+
mmCache.set(pattern, (mm = new Minimatch(pattern, { dot: true })));
|
|
28
|
+
return mm;
|
|
29
|
+
}
|
|
30
|
+
/** includePaths: exact path or case-sensitive glob. */
|
|
31
|
+
function includeHit(file, pattern) {
|
|
32
|
+
if (file === pattern)
|
|
33
|
+
return "exact";
|
|
34
|
+
return glob(pattern).match(file) ? "glob" : null;
|
|
35
|
+
}
|
|
36
|
+
/** ignorePaths: substring OR case-sensitive glob. */
|
|
37
|
+
function ignoreHit(file, pattern) {
|
|
38
|
+
return { substring: file.includes(pattern), glob: glob(pattern).match(file) };
|
|
39
|
+
}
|
|
40
|
+
/** Renovate's `getFilteredFileList`: includePaths first, then ignorePaths. */
|
|
41
|
+
export function filterFileList(files, includePaths, ignorePaths) {
|
|
42
|
+
const included = includePaths.length
|
|
43
|
+
? files.filter((f) => includePaths.some((p) => includeHit(f, p)))
|
|
44
|
+
: files;
|
|
45
|
+
return ignorePaths.length
|
|
46
|
+
? included.filter((f) => !ignorePaths.some((p) => {
|
|
47
|
+
const h = ignoreHit(f, p);
|
|
48
|
+
return h.substring || h.glob;
|
|
49
|
+
}))
|
|
50
|
+
: included;
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// File list
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Renovate's local platform lists files with `git ls-files` and falls back to
|
|
57
|
+
* a `**` glob (dot files included, nothing pruned) — see
|
|
58
|
+
* node_modules/renovate/dist/modules/platform/local/scm.js. Mirror both so
|
|
59
|
+
* `**\/node_modules/**` style ignores are visible, unlike the
|
|
60
|
+
* .gitignore-honouring walk in customManagerPreview.ts.
|
|
61
|
+
*/
|
|
62
|
+
export async function listRepoFiles(repoPath) {
|
|
63
|
+
try {
|
|
64
|
+
const { stdout } = await execFileP("git", ["ls-files", "-z"], {
|
|
65
|
+
cwd: repoPath,
|
|
66
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
67
|
+
});
|
|
68
|
+
return { files: stdout.split("\0").filter(Boolean), source: "git" };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
const entries = await fs.readdir(repoPath, { recursive: true, withFileTypes: true });
|
|
72
|
+
const files = entries
|
|
73
|
+
.filter((e) => e.isFile() || e.isSymbolicLink())
|
|
74
|
+
.map((e) => path.relative(repoPath, path.join(e.parentPath, e.name)).split(path.sep).join("/"))
|
|
75
|
+
// ponytail: Renovate's glob would include .git/ internals too; they only add noise.
|
|
76
|
+
.filter((f) => !f.startsWith(".git/"))
|
|
77
|
+
.sort();
|
|
78
|
+
return { files, source: "walk" };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function isLeaf(node) {
|
|
82
|
+
return isRecord(node) && "setBy" in node && Array.isArray(node.setBy);
|
|
83
|
+
}
|
|
84
|
+
/** Last contribution whose (array) value contains `el` — works for overwritten and concatenated arrays. */
|
|
85
|
+
function sourceOf(leaf, el) {
|
|
86
|
+
for (let i = leaf.setBy.length - 1; i >= 0; i--) {
|
|
87
|
+
const c = leaf.setBy[i];
|
|
88
|
+
if (Array.isArray(c.value) ? c.value.some((v) => isDeepStrictEqual(v, el)) : isDeepStrictEqual(c.value, el)) {
|
|
89
|
+
return { source: c.source, via: c.via };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { source: "<unknown>", via: [] };
|
|
93
|
+
}
|
|
94
|
+
function stringList(leaf) {
|
|
95
|
+
return Array.isArray(leaf?.value) ? leaf.value.filter((v) => typeof v === "string") : [];
|
|
96
|
+
}
|
|
97
|
+
// ponytail: naive "what was meant" heuristic; only offered for bare (non-glob) patterns.
|
|
98
|
+
function suggestGlob(pattern) {
|
|
99
|
+
const p = pattern.replace(/\/+$/, "");
|
|
100
|
+
if (p.includes("/"))
|
|
101
|
+
return `${p}/**`;
|
|
102
|
+
if (p.includes("."))
|
|
103
|
+
return `**/${p}`;
|
|
104
|
+
return `**/${p}/**`;
|
|
105
|
+
}
|
|
106
|
+
/** Only the user's own config and external presets can be edited; built-in/default dead globs are just noise. */
|
|
107
|
+
function userOwned(source) {
|
|
108
|
+
return source === "<own>" || (!source.startsWith("<") && source.includes(">"));
|
|
109
|
+
}
|
|
110
|
+
export async function explainPaths(repoPath, config, options = {}) {
|
|
111
|
+
const warnings = [];
|
|
112
|
+
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
113
|
+
const explained = await explainConfig(config, options);
|
|
114
|
+
const tree = explained.explanation;
|
|
115
|
+
// Only the merge-fallback warning matters here; the rest are about Handlebars
|
|
116
|
+
// templates, irrelevant to paths (unresolved presets have their own field).
|
|
117
|
+
warnings.push(...explained.warnings.filter((w) => w.preset === "<merge>").map((w) => w.message));
|
|
118
|
+
const includeLeaf = isLeaf(tree.includePaths) ? tree.includePaths : undefined;
|
|
119
|
+
const ignoreLeaf = isLeaf(tree.ignorePaths) ? tree.ignorePaths : undefined;
|
|
120
|
+
const includePaths = stringList(includeLeaf);
|
|
121
|
+
// ignorePaths is non-mergeable with a non-empty default: unset → Renovate's default applies.
|
|
122
|
+
const ignorePaths = ignoreLeaf ? stringList(ignoreLeaf) : [...OPTIONS.ignorePaths.default];
|
|
123
|
+
const attribute = (leaf, p) => leaf ? sourceOf(leaf, p) : { source: RENOVATE_DEFAULT, via: [] };
|
|
124
|
+
for (const [key, node] of Object.entries(tree)) {
|
|
125
|
+
if (isLeaf(node) || !isRecord(node))
|
|
126
|
+
continue;
|
|
127
|
+
for (const field of ["includePaths", "ignorePaths"]) {
|
|
128
|
+
if (field in node) {
|
|
129
|
+
warnings.push(`${key}.${field} is a manager-level override (it replaces the top-level list for the ${key} manager only) and is not evaluated here.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const listed = await listRepoFiles(repoPath);
|
|
134
|
+
if (listed.source === "walk") {
|
|
135
|
+
warnings.push("repoPath is not a git work tree (or git is unavailable), so the file list comes from a full directory walk — Renovate's own fallback. In a git repo Renovate scans only `git ls-files` output.");
|
|
136
|
+
}
|
|
137
|
+
const truncated = listed.files.length > maxFiles;
|
|
138
|
+
const files = listed.files.slice(0, maxFiles);
|
|
139
|
+
if (truncated) {
|
|
140
|
+
warnings.push(`The repo lists ${listed.files.length} files; only the first ${maxFiles} were evaluated. Raise maxFiles for exact counts.`);
|
|
141
|
+
}
|
|
142
|
+
const patterns = [];
|
|
143
|
+
// includePaths, against the full list.
|
|
144
|
+
includePaths.forEach((pattern, index) => {
|
|
145
|
+
const matched = files.filter((f) => includeHit(f, pattern));
|
|
146
|
+
patterns.push({
|
|
147
|
+
field: "includePaths",
|
|
148
|
+
index,
|
|
149
|
+
pattern,
|
|
150
|
+
...attribute(includeLeaf, pattern),
|
|
151
|
+
matchKind: matched.length === 0 ? "none" : matched.includes(pattern) ? "exact" : "glob",
|
|
152
|
+
matchedCount: matched.length,
|
|
153
|
+
sampleFiles: matched.slice(0, SAMPLE_SIZE),
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
const included = filterFileList(files, includePaths, []);
|
|
157
|
+
// ignorePaths, against the include-filtered list (Renovate's order).
|
|
158
|
+
const globIgnored = new Set();
|
|
159
|
+
const substringHits = new Map(); // file → patterns that hit it by substring only
|
|
160
|
+
ignorePaths.forEach((pattern, index) => {
|
|
161
|
+
const matched = [];
|
|
162
|
+
let anyGlob = false;
|
|
163
|
+
for (const f of included) {
|
|
164
|
+
const h = ignoreHit(f, pattern);
|
|
165
|
+
if (h.glob) {
|
|
166
|
+
anyGlob = true;
|
|
167
|
+
globIgnored.add(f);
|
|
168
|
+
}
|
|
169
|
+
else if (h.substring) {
|
|
170
|
+
substringHits.set(f, [...(substringHits.get(f) ?? []), pattern]);
|
|
171
|
+
}
|
|
172
|
+
if (h.glob || h.substring)
|
|
173
|
+
matched.push(f);
|
|
174
|
+
}
|
|
175
|
+
patterns.push({
|
|
176
|
+
field: "ignorePaths",
|
|
177
|
+
index,
|
|
178
|
+
pattern,
|
|
179
|
+
...attribute(ignoreLeaf, pattern),
|
|
180
|
+
matchKind: matched.length === 0 ? "none" : anyGlob ? "glob" : "substring",
|
|
181
|
+
matchedCount: matched.length,
|
|
182
|
+
sampleFiles: matched.slice(0, SAMPLE_SIZE),
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
const kept = filterFileList(included, [], ignorePaths);
|
|
186
|
+
const allSubstringOnly = [];
|
|
187
|
+
for (const [file, pats] of substringHits) {
|
|
188
|
+
if (globIgnored.has(file))
|
|
189
|
+
continue;
|
|
190
|
+
for (const pattern of pats) {
|
|
191
|
+
const suggestedGlob = suggestGlob(pattern);
|
|
192
|
+
allSubstringOnly.push({
|
|
193
|
+
file,
|
|
194
|
+
pattern,
|
|
195
|
+
suggestedGlob,
|
|
196
|
+
suggestedGlobMatches: new Minimatch(suggestedGlob, { dot: true }).match(file),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Likely surprises first.
|
|
201
|
+
allSubstringOnly.sort((a, b) => Number(a.suggestedGlobMatches) - Number(b.suggestedGlobMatches));
|
|
202
|
+
if (allSubstringOnly.length > MAX_SUBSTRING_HITS) {
|
|
203
|
+
warnings.push(`${allSubstringOnly.length} substring-only ignorePaths hits; listing the first ${MAX_SUBSTRING_HITS}.`);
|
|
204
|
+
}
|
|
205
|
+
// packageRules[].matchFileNames, against the kept files (matchRegexOrGlob, case-insensitive).
|
|
206
|
+
const matchFileNames = [];
|
|
207
|
+
const rulesLeaf = isLeaf(tree.packageRules) ? tree.packageRules : undefined;
|
|
208
|
+
const rules = Array.isArray(rulesLeaf?.value) ? rulesLeaf.value : [];
|
|
209
|
+
for (const [rule, r] of rules.entries()) {
|
|
210
|
+
if (!isRecord(r) || !Array.isArray(r.matchFileNames))
|
|
211
|
+
continue;
|
|
212
|
+
const { source } = sourceOf(rulesLeaf, r);
|
|
213
|
+
for (const [index, pattern] of r.matchFileNames.entries()) {
|
|
214
|
+
if (typeof pattern !== "string")
|
|
215
|
+
continue;
|
|
216
|
+
let matched = [];
|
|
217
|
+
try {
|
|
218
|
+
matched = await matchFilePatterns(kept, [pattern], DEFAULT_MATCH_TIMEOUT_MS, warnings);
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
warnings.push(`packageRules[${rule}].matchFileNames[${index}] ${pattern}: ${err.message}`);
|
|
222
|
+
}
|
|
223
|
+
matchFileNames.push({
|
|
224
|
+
rule,
|
|
225
|
+
...(typeof r.description === "string" ? { description: r.description } : {}),
|
|
226
|
+
source,
|
|
227
|
+
index,
|
|
228
|
+
pattern,
|
|
229
|
+
matchedCount: matched.length,
|
|
230
|
+
sampleFiles: matched.slice(0, SAMPLE_SIZE),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const deadPatterns = [
|
|
235
|
+
...patterns
|
|
236
|
+
.filter((p) => p.matchedCount === 0 && userOwned(p.source))
|
|
237
|
+
.map(({ field, index, pattern, source }) => ({ field, index, pattern, source })),
|
|
238
|
+
...matchFileNames
|
|
239
|
+
.filter((m) => m.matchedCount === 0 && userOwned(m.source))
|
|
240
|
+
.map(({ rule, index, pattern, source }) => ({
|
|
241
|
+
field: `packageRules[${rule}].matchFileNames`,
|
|
242
|
+
index,
|
|
243
|
+
pattern,
|
|
244
|
+
source,
|
|
245
|
+
})),
|
|
246
|
+
];
|
|
247
|
+
return {
|
|
248
|
+
patterns,
|
|
249
|
+
deadPatterns,
|
|
250
|
+
substringOnlyHits: allSubstringOnly.slice(0, MAX_SUBSTRING_HITS),
|
|
251
|
+
matchFileNames,
|
|
252
|
+
summary: {
|
|
253
|
+
fileListSource: listed.source,
|
|
254
|
+
totalFiles: listed.files.length,
|
|
255
|
+
evaluatedFiles: files.length,
|
|
256
|
+
truncated,
|
|
257
|
+
keptCount: kept.length,
|
|
258
|
+
excludedByIncludePaths: files.length - included.length,
|
|
259
|
+
excludedByIgnorePaths: included.length - kept.length,
|
|
260
|
+
substringOnlyHitCount: allSubstringOnly.length,
|
|
261
|
+
},
|
|
262
|
+
mergeQuality: explained.mergeQuality,
|
|
263
|
+
presetsUnresolved: explained.presetsUnresolved,
|
|
264
|
+
warnings,
|
|
265
|
+
};
|
|
266
|
+
}
|
package/dist/lib/setupCheck.js
CHANGED
package/dist/lib/version.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
-
export const SERVER_VERSION = "1.
|
|
2
|
+
export const SERVER_VERSION = "1.8.0"; // x-release-please-version
|
|
3
3
|
// "released" = the server is running from a `node_modules/` install (global
|
|
4
4
|
// `npm i -g`, project-local `npm i`, or `npx renovate-mcp`). Anything else —
|
|
5
5
|
// most commonly an MCP config of `{"command": "node", "args": [".../dist/index.js"]}`
|
|
@@ -33,7 +33,7 @@ export function registerWorkflowPrompts(server) {
|
|
|
33
33
|
? `3. Call suggest_presets with query "${intent}" to get ranked presets and, if coverage is weak, an unvalidated draft config skeleton.`
|
|
34
34
|
: '3. Ask the user what they want Renovate to do, then call suggest_presets with that intent as the query.',
|
|
35
35
|
"4. Call resolve_config and explain_config on the resulting draft/config so you can show what it actually resolves to and which preset set each field.",
|
|
36
|
-
"5. Call validate_config and lint_config against the draft to catch schema errors and Renovate-specific footguns.",
|
|
36
|
+
"5. Call validate_config and lint_config against the draft to catch schema errors and Renovate-specific footguns, and explain_paths with repoPath (plus the draft as configContent) to check which files its includePaths / ignorePaths keep — presets like config:recommended silently ignore test/ and examples/ directories.",
|
|
37
37
|
"6. Call dry_run against the repo to preview the actual updates Renovate would propose.",
|
|
38
38
|
"7. Summarize the plan (presets used, resolved effective config, dry-run results) and ask the user to confirm.",
|
|
39
39
|
"8. Only after the user explicitly confirms, call write_config with repoPath to save. Never call write_config before that confirmation.",
|
|
@@ -105,7 +105,7 @@ export function registerWorkflowPrompts(server) {
|
|
|
105
105
|
intent
|
|
106
106
|
? `4. Call suggest_presets with a query combining the detected ecosystems and the user's intent "${intent}" (e.g. "npm and github-actions monorepo, automerge patch updates, pin digests"), and use the returned draft.config as the starting point.`
|
|
107
107
|
: '4. Ask the user what they want Renovate to do, then call suggest_presets with a query combining the detected ecosystems and that intent (e.g. "npm and github-actions monorepo, automerge patch updates, pin digests"), and use the returned draft.config as the starting point.',
|
|
108
|
-
"5. Call lint_config and validate_config against the draft and fix any findings.",
|
|
108
|
+
"5. Call lint_config and validate_config against the draft and fix any findings. Then call explain_paths with repoPath and the draft as configContent to confirm its ignorePaths (including the ones presets add) do not drop package files detected in step 3.",
|
|
109
109
|
"6. Summarize the draft config and the detected ecosystems, and ask the user to confirm. dry_run has no inline config input, so the draft must be written before a full dry run can use it.",
|
|
110
110
|
"7. Only after the user explicitly confirms, call write_config with repoPath to save. Never call write_config before that confirmation.",
|
|
111
111
|
`8. Call dry_run with repoPath "${repoPath}" (default full mode) to preview the actual updates Renovate would propose with the new config, and report the result.`,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { configRecord, endpointString, pathString } from "../lib/inputLimits.js";
|
|
3
|
+
import { DEFAULT_MAX_FILES, explainPaths } from "../lib/pathSelection.js";
|
|
4
|
+
import { FETCH_PLATFORMS } from "../lib/presetResolver.js";
|
|
5
|
+
import { assertRepoDir, loadConfigSource, MERGE_PREVIEW_DISCLAIMER, NETWORK_READ_ONLY_ANNOTATIONS, } from "../lib/toolInputs.js";
|
|
6
|
+
const EXPLAIN_PATHS_DISCLAIMER = "Files are listed the way Renovate's local platform does (`git ls-files`, else a full walk) and filtered with a mirror of Renovate's own getIncludedFiles / filterIgnoredFiles; the config is resolved with Renovate's mergeChildConfig. Remote platforms list the base branch's committed tree, so uncommitted files may differ. Built-in managers' own managerFilePatterns are not applied — run dry_run with dryRunMode \"extract\" for the files each manager actually extracts.";
|
|
7
|
+
export function registerExplainPaths(server) {
|
|
8
|
+
server.registerTool("explain_paths", {
|
|
9
|
+
title: "Explain which files Renovate scans",
|
|
10
|
+
annotations: NETWORK_READ_ONLY_ANNOTATIONS,
|
|
11
|
+
description: "Offline: resolve the repo's config (presets included, so hidden ignorePaths like `:ignoreModulesAndTests` from config:recommended show up), list the repo's files the way Renovate does (`git ls-files`), and apply Renovate's includePaths → ignorePaths filter. Returns every pattern with the preset or `<own>` config that set it, how many files it matched and a sample; `deadPatterns` (user-owned patterns matching no file, including packageRules[].matchFileNames entries); `substringOnlyHits` (files dropped only because an ignorePaths entry is a substring of the path, e.g. `test` dropping `src/latest/package.json`, with the glob that was probably meant); per-rule matchFileNames counts against the kept files; and a summary. Pass `configContent` to try a config other than the repo's own. Same `externalPresets` / `endpoint` / `platform` opt-ins as resolve_config.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
repoPath: pathString("Absolute path to the repository root whose files are evaluated."),
|
|
14
|
+
configContent: configRecord("Inline config to evaluate instead of the repo's own config file.").optional(),
|
|
15
|
+
externalPresets: z
|
|
16
|
+
.boolean()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("When true, fetch external presets (github>, gitlab>, gitea>, forgejo>) over HTTPS — same as resolve_config. Default false."),
|
|
19
|
+
endpoint: endpointString("API base URL for external preset fetches — same semantics as resolve_config.").optional(),
|
|
20
|
+
platform: z
|
|
21
|
+
.enum(FETCH_PLATFORMS)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Platform flavour of `endpoint`; routes `local>` presets — same semantics as resolve_config."),
|
|
24
|
+
maxFiles: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.min(1)
|
|
28
|
+
.max(1_000_000)
|
|
29
|
+
.optional()
|
|
30
|
+
.describe(`Cap on the number of listed files evaluated (default ${DEFAULT_MAX_FILES}); truncation is reported.`),
|
|
31
|
+
},
|
|
32
|
+
}, async ({ repoPath, configContent, externalPresets, endpoint, platform, maxFiles }) => {
|
|
33
|
+
const repoError = await assertRepoDir(repoPath);
|
|
34
|
+
if (repoError)
|
|
35
|
+
return { isError: true, content: [{ type: "text", text: repoError }] };
|
|
36
|
+
const src = configContent !== undefined ? { config: configContent } : await loadConfigSource({ repoPath });
|
|
37
|
+
if ("error" in src)
|
|
38
|
+
return { isError: true, content: [{ type: "text", text: src.error }] };
|
|
39
|
+
const result = await explainPaths(repoPath, src.config, {
|
|
40
|
+
fetchExternal: externalPresets ?? false,
|
|
41
|
+
endpoint,
|
|
42
|
+
platform,
|
|
43
|
+
maxFiles,
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify({
|
|
50
|
+
...("path" in src && src.path ? { path: src.path } : {}),
|
|
51
|
+
...result,
|
|
52
|
+
disclaimer: result.mergeQuality === "faithful" ? EXPLAIN_PATHS_DISCLAIMER : MERGE_PREVIEW_DISCLAIMER,
|
|
53
|
+
}, null, 2),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "renovate-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "MCP server for designing Renovate configurations interactively.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"jsonata": "2.2.1",
|
|
67
67
|
"jsonc-parser": "^3.3.1",
|
|
68
68
|
"minimatch": "^10.2.6",
|
|
69
|
-
"renovate": "44.
|
|
69
|
+
"renovate": "44.198.3",
|
|
70
70
|
"smol-toml": "^1.6.1",
|
|
71
71
|
"yaml": "^2.9.0",
|
|
72
72
|
"zod": "^4.0.0"
|