renovate-mcp 1.5.6 → 1.6.1

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.
Files changed (109) hide show
  1. package/README.md +10 -8
  2. package/dist/data/managers.generated.js +2 -2
  3. package/dist/data/migrations.generated.js +95 -7
  4. package/dist/data/options.generated.js +13 -3
  5. package/dist/data/presets.generated.js +2 -2
  6. package/dist/lib/configExplainer.js +9 -15
  7. package/dist/lib/configLinter.js +38 -30
  8. package/dist/lib/configLocations.js +8 -7
  9. package/dist/lib/configWriter.js +27 -58
  10. package/dist/lib/customManagerPreview.js +226 -154
  11. package/dist/lib/dependencyExplainer.js +1 -1
  12. package/dist/lib/dryRunDiff.js +0 -0
  13. package/dist/lib/endpointProbe.js +11 -15
  14. package/dist/lib/endpointValidator.js +38 -58
  15. package/dist/lib/externalPresetFetcher.js +56 -112
  16. package/dist/lib/hostRulesConfig.js +10 -7
  17. package/dist/lib/lookupProblems.js +6 -8
  18. package/dist/lib/mergeWorker.js +3 -59
  19. package/dist/lib/mergeWorkerImpl.js +1 -1
  20. package/dist/lib/migrationWorker.js +3 -58
  21. package/dist/lib/packageRulesAnalysis.js +30 -4
  22. package/dist/lib/packageRulesWorker.js +6 -52
  23. package/dist/lib/packageRulesWorkerImpl.js +3 -7
  24. package/dist/lib/presetResolver.js +23 -28
  25. package/dist/lib/presetSuggester.js +3 -8
  26. package/dist/lib/renovateCli.js +121 -13
  27. package/dist/lib/renovateWorker.js +61 -0
  28. package/dist/lib/runtimeWarnings.js +6 -17
  29. package/dist/lib/setupCheck.js +19 -2
  30. package/dist/lib/toolInputs.js +41 -0
  31. package/dist/lib/uriVariable.js +14 -0
  32. package/dist/lib/util.js +10 -0
  33. package/dist/lib/version.js +1 -1
  34. package/dist/prompts/workflows.js +1 -1
  35. package/dist/resources/options.js +3 -7
  36. package/dist/resources/presets.js +7 -15
  37. package/dist/tools/annotateDryRun.js +14 -38
  38. package/dist/tools/checkSetup.js +3 -7
  39. package/dist/tools/dryRun.js +89 -81
  40. package/dist/tools/dryRunDiff.js +24 -25
  41. package/dist/tools/explainConfig.js +8 -35
  42. package/dist/tools/explainDependency.js +20 -28
  43. package/dist/tools/lintConfig.js +11 -6
  44. package/dist/tools/migrateConfig.js +6 -0
  45. package/dist/tools/previewCustomManager.js +15 -7
  46. package/dist/tools/readConfig.js +4 -0
  47. package/dist/tools/resolveConfig.js +8 -35
  48. package/dist/tools/resolveConfigDiff.js +6 -19
  49. package/dist/tools/testPackageRules.js +12 -47
  50. package/dist/tools/validateConfig.js +22 -5
  51. package/dist/tools/writeConfig.js +45 -20
  52. package/package.json +4 -6
  53. package/dist/data/managers.generated.d.ts +0 -12
  54. package/dist/data/migrations.generated.d.ts +0 -15
  55. package/dist/data/options.generated.d.ts +0 -21
  56. package/dist/data/presets.generated.d.ts +0 -8
  57. package/dist/index.d.ts +0 -2
  58. package/dist/lib/configExplainer.d.ts +0 -63
  59. package/dist/lib/configLinter.d.ts +0 -11
  60. package/dist/lib/configLocations.d.ts +0 -9
  61. package/dist/lib/configWriter.d.ts +0 -48
  62. package/dist/lib/credentialResolver.d.ts +0 -13
  63. package/dist/lib/customManagerPreview.d.ts +0 -80
  64. package/dist/lib/dependencyExplainer.d.ts +0 -48
  65. package/dist/lib/dryRunDiff.d.ts +0 -79
  66. package/dist/lib/dryRunProgress.d.ts +0 -24
  67. package/dist/lib/endpointProbe.d.ts +0 -20
  68. package/dist/lib/endpointValidator.d.ts +0 -17
  69. package/dist/lib/externalPresetFetcher.d.ts +0 -22
  70. package/dist/lib/gitRemote.d.ts +0 -28
  71. package/dist/lib/hostRulesConfig.d.ts +0 -26
  72. package/dist/lib/inputLimits.d.ts +0 -22
  73. package/dist/lib/log.d.ts +0 -1
  74. package/dist/lib/lookupProblems.d.ts +0 -19
  75. package/dist/lib/mergeWorker.d.ts +0 -34
  76. package/dist/lib/mergeWorkerImpl.d.ts +0 -1
  77. package/dist/lib/migrationWorker.d.ts +0 -22
  78. package/dist/lib/migrationWorkerImpl.d.ts +0 -1
  79. package/dist/lib/packageRulesAnalysis.d.ts +0 -89
  80. package/dist/lib/packageRulesWorker.d.ts +0 -49
  81. package/dist/lib/packageRulesWorkerImpl.d.ts +0 -1
  82. package/dist/lib/presetResolver.d.ts +0 -145
  83. package/dist/lib/presetSuggester.d.ts +0 -125
  84. package/dist/lib/renovateCli.d.ts +0 -71
  85. package/dist/lib/reportInput.d.ts +0 -13
  86. package/dist/lib/resolveConfigDiff.d.ts +0 -50
  87. package/dist/lib/runtimeWarnings.d.ts +0 -45
  88. package/dist/lib/setupCheck.d.ts +0 -139
  89. package/dist/lib/version.d.ts +0 -9
  90. package/dist/prompts/workflows.d.ts +0 -2
  91. package/dist/resources/options.d.ts +0 -9
  92. package/dist/resources/presets.d.ts +0 -2
  93. package/dist/tools/annotateDryRun.d.ts +0 -2
  94. package/dist/tools/checkSetup.d.ts +0 -2
  95. package/dist/tools/dryRun.d.ts +0 -10
  96. package/dist/tools/dryRunDiff.d.ts +0 -2
  97. package/dist/tools/explainConfig.d.ts +0 -2
  98. package/dist/tools/explainDependency.d.ts +0 -2
  99. package/dist/tools/getVersion.d.ts +0 -2
  100. package/dist/tools/lintConfig.d.ts +0 -2
  101. package/dist/tools/migrateConfig.d.ts +0 -2
  102. package/dist/tools/previewCustomManager.d.ts +0 -2
  103. package/dist/tools/readConfig.d.ts +0 -2
  104. package/dist/tools/resolveConfig.d.ts +0 -2
  105. package/dist/tools/resolveConfigDiff.d.ts +0 -2
  106. package/dist/tools/suggestPresets.d.ts +0 -2
  107. package/dist/tools/testPackageRules.d.ts +0 -2
  108. package/dist/tools/validateConfig.d.ts +0 -2
  109. package/dist/tools/writeConfig.d.ts +0 -2
package/README.md CHANGED
@@ -32,7 +32,7 @@ Restart your client and try the prompt: *"List the namespaces available under `r
32
32
 
33
33
  - **Read and explain configs** — locate the active `renovate.json*`, expand every `extends` preset offline, and trace which preset set each field.
34
34
  - **Discover presets from intent** — describe what you want ("automerge patches, group dev deps") and get ranked built-in (and your own local-repo) presets, plus a draft config skeleton when nothing fits.
35
- - **Preview custom managers** before running Renovate — regex and JSONata, with file/line hits and extracted dep info.
35
+ - **Preview custom managers** before running Renovate — regex and JSONata, with Renovate's `managerFilePatterns` glob/regex file selection, file/line hits and extracted dep info.
36
36
  - **Validate and lint** — schema validation plus a semantic lint pass for Renovate-specific footguns (unwrapped regexes, unknown manager names, deprecated keys).
37
37
  - **Dry-run** against a local checkout or a remote GitHub/GitLab — see exactly which PRs Renovate would open.
38
38
  - **Save back atomically** — round-trip writes preserve comments and key order in existing JSON-with-comments files.
@@ -80,7 +80,7 @@ Seventeen tools, six resources, and three workflow prompts. Each tool name below
80
80
  - **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.
81
81
  - **Node.js ≥ 24** (aligns with Renovate's own engine requirement).
82
82
 
83
- 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`, `preview_custom_manager`, `lint_config`, `explain_dependency`) never spawn Renovate at all.
83
+ 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`, `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).
84
84
 
85
85
  **Optional env vars:**
86
86
 
@@ -156,8 +156,9 @@ Once the server is wired up, try prompts like these. Written for Claude but work
156
156
 
157
157
  **Authoring a custom manager (regex or JSONata)**
158
158
 
159
- - "I have `# renovate: datasource=docker depName=...` comments above image tags in my Dockerfiles. Draft a `customManagers` regex entry and preview it against this repo so I can see what it extracts."
160
- - "Here's a `customManagers` entry — preview it and tell me which files match, which lines hit each `matchStrings` regex, and what dep info gets extracted."
159
+ - "I have `# renovate: datasource=docker depName=...` comments above image tags in my Dockerfiles. Draft a `customManagers` regex entry with `managerFilePatterns: ["**/Dockerfile"]` and preview it against this repo so I can see what it extracts."
160
+ - "Here's a `customManagers` entry — preview it and tell me which files match `managerFilePatterns`, which lines hit each `matchStrings` regex, and what dep info gets extracted."
161
+ - "My custom manager still uses `fileMatch`. Lint the config, then run `migrate_config` so it becomes `managerFilePatterns`."
161
162
  - "Draft a `customType: \"jsonata\"` customManager with `fileFormat: \"yaml\"` to extract Helm chart dependencies (`name`, `version`, `repository`) from `Chart.yaml`, and preview it against this repo."
162
163
 
163
164
  **Validating, previewing, saving**
@@ -173,8 +174,8 @@ A transcript-style walkthrough: design a Dockerfile custom manager from scratch,
173
174
 
174
175
  > **You:** I've got `# renovate: datasource=docker depName=<image>` comments above `FROM` lines in my Dockerfiles. Draft a `customManagers` entry and preview it against this repo.
175
176
  >
176
- > **Claude** calls `preview_custom_manager` with a first-draft `fileMatch` + `matchStrings`.
177
- > → 4 Dockerfiles matched `fileMatch`, 0 lines matched `matchStrings`. The regex anchored on `ARG`, but the Dockerfiles use `FROM`.
177
+ > **Claude** calls `preview_custom_manager` with a first-draft `managerFilePatterns` (`**/Dockerfile`) + `matchStrings`.
178
+ > → 4 Dockerfiles matched `managerFilePatterns`, 0 lines matched `matchStrings`. The regex anchored on `ARG`, but the Dockerfiles use `FROM`.
178
179
  >
179
180
  > **You:** Rewrite `matchStrings` to anchor on the renovate comment, then `FROM <image>:<version>` on the next line.
180
181
  >
@@ -207,6 +208,7 @@ npm run generate:presets # regenerate src/data/presets.generated.ts
207
208
  npm run generate:managers # regenerate src/data/managers.generated.ts
208
209
  npm run generate:migrations # regenerate src/data/migrations.generated.ts
209
210
  npm run generate:options # regenerate src/data/options.generated.ts
211
+ npm run sync:jsonata-pin # sync package.json's jsonata pin to Renovate's own
210
212
  npm run check:snapshot-versions # fail if any src/data/*.generated.ts is stale vs installed renovate
211
213
  ```
212
214
 
@@ -225,5 +227,5 @@ See [`docs/development.md`](docs/development.md) for snapshot-file mechanics, CI
225
227
 
226
228
  - **Not a Renovate replacement.** This server doesn't open PRs, run scheduled updates, or execute in CI — it's a design-time companion for a local `renovate.json`. Use the real Renovate for the actual dependency-update pipeline.
227
229
  - **`resolve_config` merges faithfully.** Preset expansion runs offline against a committed snapshot, then folds with Renovate's real `mergeChildConfig` in a worker thread — `mergeQuality: "faithful"` (it falls back to an approximate in-process merge as `"preview"` only if the worker is unavailable). Template substitution still implements only positional `{{argN}}` placeholders — non-positional tokens and Handlebars helpers are flagged in `warnings` and pass through verbatim. For full config resolution (datasource lookups, etc.), run `dry_run`.
228
- - **`preview_custom_manager` is a subset of Renovate's custom managers.** It covers `customType: "regex"` (with `matchStringsStrategy` of `any` / `combination` / `recursive`) and `customType: "jsonata"` (with `fileFormat: "json" | "yaml" | "toml"`). Template substitution is `{{groupName}}` only — full Handlebars helpers/conditionals are not implemented. Other custom types (e.g. `html`) are out of scope. Use it for fast iteration; confirm with `dry_run`. Full coverage matrix in [`docs/tools.md#preview_custom_manager`](docs/tools.md#preview_custom_manager).
229
- - **`validate_config` / `dry_run` aren't exercised end-to-end in CI.** The bundled Renovate is available (it's a runtime dep), but the integration tests use fake binaries via `RENOVATE_BIN` / `RENOVATE_CONFIG_VALIDATOR_BIN` env overrides for determinism and speed. Run the tools locally against a real config to validate behaviour.
230
+ - **`preview_custom_manager` is a subset of Renovate's custom managers.** It covers `customType: "regex"` (with `matchStringsStrategy` of `any` / `combination` / `recursive`) and `customType: "jsonata"` (with `fileFormat: "json" | "yaml" | "toml"`). File selection follows Renovate's `managerFilePatterns` semantics (globs or `/regex/`, entries unioned like Renovate's extract phase); the deprecated `fileMatch` is still accepted with a warning. Template substitution is `{{groupName}}` only — full Handlebars helpers/conditionals are not implemented. Other custom types (e.g. `html`) are out of scope. Use it for fast iteration; confirm with `dry_run`. Full coverage matrix in [`docs/tools.md#preview_custom_manager`](docs/tools.md#preview_custom_manager).
231
+ - **`dry_run` still uses fake binaries in CI.** The bundled Renovate is available (it's a runtime dependency), and `validate_config` / `write_config` now have one real-validator integration test (`test/integration/realValidator.test.ts`) exercising the actual bundled `renovate-config-validator` — but `dry_run` itself still runs per-PR against fake binaries via `RENOVATE_BIN` / `RENOVATE_CONFIG_VALIDATOR_BIN` env overrides, for determinism and speed. A separate nightly workflow re-runs the full suite against `renovate@latest` end-to-end. Run `dry_run` locally against a real config to validate behaviour.
@@ -1,6 +1,6 @@
1
- // AUTO-GENERATED by scripts/generate-managers.mjs from renovate@44.103.6.
1
+ // AUTO-GENERATED by scripts/generate-managers.mjs from renovate@44.115.10.
2
2
  // Do not edit by hand. Run `npm run generate:managers` to refresh.
3
- export const RENOVATE_VERSION = "44.103.6";
3
+ export const RENOVATE_VERSION = "44.115.10";
4
4
  /**
5
5
  * All manager names accepted by Renovate's `matchManagers` /
6
6
  * `excludeManagers` fields. Includes both regular managers and custom
@@ -1,12 +1,13 @@
1
- // AUTO-GENERATED by scripts/generate-migrations.mjs from renovate@44.103.6.
1
+ // AUTO-GENERATED by scripts/generate-migrations.mjs from renovate@44.115.10.
2
2
  // Do not edit by hand. Run `npm run generate:migrations` to refresh.
3
- export const RENOVATE_VERSION = "44.103.6";
3
+ export const RENOVATE_VERSION = "44.115.10";
4
4
  /**
5
- * Deprecated config keys with their current replacement. Sourced from
6
- * Renovate's `MigrationsService.renamedProperties` — the "RenamePropertyMigration"
7
- * subset only. Value coercions (e.g. `stabilityDays` → `minimumReleaseAge`)
8
- * and structural transformations are deliberately excluded; `migrate_config`
9
- * is the right tool for those.
5
+ * Deprecated config keys. Sourced from Renovate's `MigrationsService`:
6
+ * `renamedProperties` (plain renames, with `newKey`) plus every
7
+ * `customMigrations` class flagged `deprecated` (value coercions such as
8
+ * `stabilityDays` → `minimumReleaseAge`, structural rewrites such as
9
+ * `fileMatch` → `managerFilePatterns`; no `newKey` — `migrate_config`
10
+ * shows the replacement).
10
11
  */
11
12
  export const DEPRECATED_KEYS = Object.freeze([
12
13
  {
@@ -21,14 +22,29 @@ export const DEPRECATED_KEYS = Object.freeze([
21
22
  "oldKey": "allowedPostUpgradeCommands",
22
23
  "newKey": "allowedCommands"
23
24
  },
25
+ {
26
+ "oldKey": "automergeMajor"
27
+ },
28
+ {
29
+ "oldKey": "automergeMinor"
30
+ },
31
+ {
32
+ "oldKey": "automergePatch"
33
+ },
24
34
  {
25
35
  "oldKey": "azureAutoApprove",
26
36
  "newKey": "autoApprove"
27
37
  },
38
+ {
39
+ "oldKey": "baseBranch"
40
+ },
28
41
  {
29
42
  "oldKey": "baseBranches",
30
43
  "newKey": "baseBranchPatterns"
31
44
  },
45
+ {
46
+ "oldKey": "compatibility"
47
+ },
32
48
  {
33
49
  "oldKey": "customChangelogUrl",
34
50
  "newKey": "changelogUrl"
@@ -45,6 +61,24 @@ export const DEPRECATED_KEYS = Object.freeze([
45
61
  "oldKey": "exposeEnv",
46
62
  "newKey": "exposeAllEnv"
47
63
  },
64
+ {
65
+ "oldKey": "fetchReleaseNotes"
66
+ },
67
+ {
68
+ "oldKey": "fileMatch"
69
+ },
70
+ {
71
+ "oldKey": "gomodTidy"
72
+ },
73
+ {
74
+ "oldKey": "ignoreNodeModules"
75
+ },
76
+ {
77
+ "oldKey": "ignoreNpmrcFile"
78
+ },
79
+ {
80
+ "oldKey": "includeForks"
81
+ },
48
82
  {
49
83
  "oldKey": "keepalive",
50
84
  "newKey": "keepAlive"
@@ -89,6 +123,33 @@ export const DEPRECATED_KEYS = Object.freeze([
89
123
  "oldKey": "multipleMajorPrs",
90
124
  "newKey": "separateMultipleMajor"
91
125
  },
126
+ {
127
+ "oldKey": "packageFiles"
128
+ },
129
+ {
130
+ "oldKey": "packageName"
131
+ },
132
+ {
133
+ "oldKey": "packagePattern"
134
+ },
135
+ {
136
+ "oldKey": "packages"
137
+ },
138
+ {
139
+ "oldKey": "pathRules"
140
+ },
141
+ {
142
+ "oldKey": "pinVersions"
143
+ },
144
+ {
145
+ "oldKey": "rebaseConflictedPrs"
146
+ },
147
+ {
148
+ "oldKey": "rebaseStalePrs"
149
+ },
150
+ {
151
+ "oldKey": "recreateClosed"
152
+ },
92
153
  {
93
154
  "oldKey": "regexManagers",
94
155
  "newKey": "customManagers"
@@ -97,12 +158,39 @@ export const DEPRECATED_KEYS = Object.freeze([
97
158
  "oldKey": "renovate-config-presets",
98
159
  "newKey": "renovate-config"
99
160
  },
161
+ {
162
+ "oldKey": "renovateFork"
163
+ },
164
+ {
165
+ "oldKey": "requiredStatusChecks"
166
+ },
167
+ {
168
+ "oldKey": "semanticPrefix"
169
+ },
100
170
  {
101
171
  "oldKey": "separatePatchReleases",
102
172
  "newKey": "separateMinorPatch"
103
173
  },
174
+ {
175
+ "oldKey": "stabilityDays"
176
+ },
177
+ {
178
+ "oldKey": "trustLevel"
179
+ },
180
+ {
181
+ "oldKey": "unpublishSafe"
182
+ },
183
+ {
184
+ "oldKey": "updateLockFiles"
185
+ },
186
+ {
187
+ "oldKey": "upgradeInRange"
188
+ },
104
189
  {
105
190
  "oldKey": "versionScheme",
106
191
  "newKey": "versioning"
192
+ },
193
+ {
194
+ "oldKey": "versionStrategy"
107
195
  }
108
196
  ]);
@@ -1,6 +1,6 @@
1
- // AUTO-GENERATED by scripts/generate-options.mjs from renovate@44.103.6.
1
+ // AUTO-GENERATED by scripts/generate-options.mjs from renovate@44.115.10.
2
2
  // Do not edit by hand. Run `npm run generate:options` to refresh.
3
- export const RENOVATE_VERSION = "44.103.6";
3
+ export const RENOVATE_VERSION = "44.115.10";
4
4
  export const OPTIONS = {
5
5
  "abandonmentThreshold": {
6
6
  "description": "Flags packages that have not been updated within this period as abandoned.",
@@ -1403,7 +1403,7 @@ export const OPTIONS = {
1403
1403
  "dockerSidecarImage": {
1404
1404
  "description": "Change this value to override the default Renovate sidecar image.",
1405
1405
  "type": "string",
1406
- "default": "ghcr.io/renovatebot/base-image:13.99.5",
1406
+ "default": "ghcr.io/renovatebot/base-image:13.102.5",
1407
1407
  "globalOnly": true,
1408
1408
  "deprecationMsg": "The usage of `binarySource=docker` is deprecated, and will be removed in the future"
1409
1409
  },
@@ -2905,6 +2905,16 @@ export const OPTIONS = {
2905
2905
  "cli": false,
2906
2906
  "env": false
2907
2907
  },
2908
+ "matchIsBreaking": {
2909
+ "description": "Matches only if the update is considered breaking (e.g. major, or minor for 0.x in ecosystems like Cargo) when `true`, or non-breaking when `false`. Valid only within a `packageRules` object.",
2910
+ "type": "boolean",
2911
+ "parents": [
2912
+ "packageRules"
2913
+ ],
2914
+ "stage": "package",
2915
+ "cli": false,
2916
+ "env": false
2917
+ },
2908
2918
  "matchJsonata": {
2909
2919
  "description": "A JSONata expression to match against the full config object. Valid only within a `packageRules` object.",
2910
2920
  "type": "array",
@@ -1,6 +1,6 @@
1
- // AUTO-GENERATED by scripts/generate-presets.mjs from renovate@44.103.6.
1
+ // AUTO-GENERATED by scripts/generate-presets.mjs from renovate@44.115.10.
2
2
  // Do not edit by hand. Run `npm run generate:presets` to refresh.
3
- export const RENOVATE_VERSION = "44.103.6";
3
+ export const RENOVATE_VERSION = "44.115.10";
4
4
  export const PRESETS = {
5
5
  "abandonments:recommended": {
6
6
  "namespace": "abandonments",
@@ -1,7 +1,7 @@
1
- import { collectMergeSteps, OWN_SOURCE, } from "./presetResolver.js";
1
+ import { collectMergeSteps, } from "./presetResolver.js";
2
2
  import { runMerge } from "./mergeWorker.js";
3
- // Re-exported for callers that historically imported the sentinel from here.
4
- export { OWN_SOURCE };
3
+ import { isRecord } from "./util.js";
4
+ import { isDeepStrictEqual } from "node:util";
5
5
  /**
6
6
  * Inverse of `resolveConfig`: instead of collapsing the preset tree into a flat
7
7
  * resolved object, annotate each leaf field with the chain of presets that set
@@ -89,16 +89,16 @@ function diffLeaves(before, after, path, onContribution) {
89
89
  for (const [key, afterVal] of Object.entries(after)) {
90
90
  const beforeVal = before[key];
91
91
  const childPath = [...path, key];
92
- if (isPlainObject(afterVal)) {
93
- diffLeaves(isPlainObject(beforeVal) ? beforeVal : {}, afterVal, childPath, onContribution);
92
+ if (isRecord(afterVal)) {
93
+ diffLeaves(isRecord(beforeVal) ? beforeVal : {}, afterVal, childPath, onContribution);
94
94
  continue;
95
95
  }
96
- if (jsonEqual(beforeVal, afterVal))
96
+ if (isDeepStrictEqual(beforeVal, afterVal))
97
97
  continue;
98
98
  if (Array.isArray(beforeVal) &&
99
99
  Array.isArray(afterVal) &&
100
100
  afterVal.length > beforeVal.length &&
101
- jsonEqual(afterVal.slice(0, beforeVal.length), beforeVal)) {
101
+ isDeepStrictEqual(afterVal.slice(0, beforeVal.length), beforeVal)) {
102
102
  // Mergeable-array concatenation: record only the tail this step appended.
103
103
  onContribution(childPath, afterVal.slice(beforeVal.length));
104
104
  }
@@ -113,7 +113,7 @@ function diffLeaves(before, after, path, onContribution) {
113
113
  * values) plus the path→contributions map produced by `diffLeaves`.
114
114
  */
115
115
  function buildAnnotated(node, path, contribByPath) {
116
- if (isPlainObject(node)) {
116
+ if (isRecord(node)) {
117
117
  const out = {};
118
118
  for (const [key, child] of Object.entries(node)) {
119
119
  out[key] = buildAnnotated(child, [...path, key], contribByPath);
@@ -122,9 +122,6 @@ function buildAnnotated(node, path, contribByPath) {
122
122
  }
123
123
  return { value: node, setBy: contribByPath.get(JSON.stringify(path)) ?? [] };
124
124
  }
125
- function jsonEqual(a, b) {
126
- return JSON.stringify(a) === JSON.stringify(b);
127
- }
128
125
  /**
129
126
  * Approximate annotated merge — the fallback when the faithful worker is
130
127
  * unavailable. Arrays concat (and concat their `setBy`), plain objects recurse,
@@ -133,7 +130,7 @@ function jsonEqual(a, b) {
133
130
  * tool produced before.
134
131
  */
135
132
  function annotateValue(value, source, via) {
136
- if (isPlainObject(value)) {
133
+ if (isRecord(value)) {
137
134
  const out = {};
138
135
  for (const [k, v] of Object.entries(value)) {
139
136
  out[k] = annotateValue(v, source, via);
@@ -175,6 +172,3 @@ function isAnnotatedLeaf(node) {
175
172
  return false;
176
173
  return Array.isArray(node.setBy);
177
174
  }
178
- function isPlainObject(v) {
179
- return typeof v === "object" && v !== null && !Array.isArray(v);
180
- }
@@ -1,5 +1,6 @@
1
1
  import { ALL_MANAGERS, CUSTOM_MANAGERS } from "../data/managers.generated.js";
2
- import { DEPRECATED_KEYS } from "../data/migrations.generated.js";
2
+ import { DEPRECATED_KEYS, } from "../data/migrations.generated.js";
3
+ import { isRecord } from "./util.js";
3
4
  const REGEX_AWARE_FIELDS = new Set([
4
5
  "matchPackageNames",
5
6
  "matchDepNames",
@@ -11,7 +12,7 @@ const VALID_MANAGER_NAMES = new Set([
11
12
  ...ALL_MANAGERS,
12
13
  ...CUSTOM_MANAGERS.map((m) => `custom.${m}`),
13
14
  ]);
14
- const DEPRECATED_KEY_LOOKUP = new Map(DEPRECATED_KEYS.map((e) => [e.oldKey, e.newKey]));
15
+ const DEPRECATED_KEY_LOOKUP = new Map(DEPRECATED_KEYS.map((e) => [e.oldKey, e]));
15
16
  // Hand-maintained snapshot of keys that count as "actions" on a packageRules entry
16
17
  // (i.e. keys whose presence means the rule actually *does* something to the matched
17
18
  // deps). Source of truth: `node_modules/renovate/dist/config/options/index.js` —
@@ -251,12 +252,12 @@ function isSelectorKey(key) {
251
252
  key === "excludePaths");
252
253
  }
253
254
  function checkPackageRuleWithoutAction(config, findings) {
254
- if (!isPlainObject(config))
255
+ if (!isRecord(config))
255
256
  return;
256
257
  if (!Array.isArray(config.packageRules))
257
258
  return;
258
259
  config.packageRules.forEach((entry, i) => {
259
- if (!isPlainObject(entry))
260
+ if (!isRecord(entry))
260
261
  return;
261
262
  const keys = Object.keys(entry);
262
263
  const selectors = [];
@@ -283,14 +284,14 @@ function checkPackageRuleWithoutAction(config, findings) {
283
284
  });
284
285
  }
285
286
  function checkContradictoryDisabled(config, findings) {
286
- if (!isPlainObject(config))
287
+ if (!isRecord(config))
287
288
  return;
288
289
  if (config.enabled !== false)
289
290
  return;
290
291
  if (!Array.isArray(config.packageRules))
291
292
  return;
292
293
  config.packageRules.forEach((entry, i) => {
293
- if (!isPlainObject(entry))
294
+ if (!isRecord(entry))
294
295
  return;
295
296
  if (entry.enabled !== true)
296
297
  return;
@@ -315,7 +316,7 @@ function makeAutomergeFinding(path) {
315
316
  };
316
317
  }
317
318
  function checkAutomergeWithoutType(config, findings) {
318
- if (!isPlainObject(config))
319
+ if (!isRecord(config))
319
320
  return;
320
321
  if (config.automerge === true && !("automergeType" in config)) {
321
322
  findings.push(makeAutomergeFinding("automerge"));
@@ -323,7 +324,7 @@ function checkAutomergeWithoutType(config, findings) {
323
324
  const pkgRules = config.packageRules;
324
325
  if (Array.isArray(pkgRules)) {
325
326
  pkgRules.forEach((entry, i) => {
326
- if (!isPlainObject(entry))
327
+ if (!isRecord(entry))
327
328
  return;
328
329
  if (entry.automerge === true && !("automergeType" in entry)) {
329
330
  findings.push(makeAutomergeFinding(`packageRules[${i}].automerge`));
@@ -332,7 +333,7 @@ function checkAutomergeWithoutType(config, findings) {
332
333
  }
333
334
  }
334
335
  function checkAutomergeIncludesMajor(config, findings) {
335
- if (!isPlainObject(config))
336
+ if (!isRecord(config))
336
337
  return;
337
338
  const pkgRules = config.packageRules;
338
339
  if (!Array.isArray(pkgRules))
@@ -344,7 +345,7 @@ function checkAutomergeIncludesMajor(config, findings) {
344
345
  // findings for unrelated entries. Upgrade path: track guard ordering /
345
346
  // selector overlap per-entry if false negatives show up in practice.
346
347
  const hasLaterMajorGuard = pkgRules.some((entry) => {
347
- if (!isPlainObject(entry))
348
+ if (!isRecord(entry))
348
349
  return false;
349
350
  if (entry.automerge !== false)
350
351
  return false;
@@ -354,7 +355,7 @@ function checkAutomergeIncludesMajor(config, findings) {
354
355
  if (hasLaterMajorGuard)
355
356
  return;
356
357
  pkgRules.forEach((entry, i) => {
357
- if (!isPlainObject(entry))
358
+ if (!isRecord(entry))
358
359
  return;
359
360
  if (entry.automerge !== true)
360
361
  return;
@@ -393,12 +394,12 @@ function normalizeSelectorValue(v) {
393
394
  return v;
394
395
  }
395
396
  function checkDuplicatePackageRuleMatchers(config, findings) {
396
- if (!isPlainObject(config))
397
+ if (!isRecord(config))
397
398
  return;
398
399
  const pkgRules = config.packageRules;
399
400
  if (!Array.isArray(pkgRules))
400
401
  return;
401
- const signatures = pkgRules.map((entry) => isPlainObject(entry) ? selectorSignature(entry) : null);
402
+ const signatures = pkgRules.map((entry) => isRecord(entry) ? selectorSignature(entry) : null);
402
403
  for (let i = 0; i < signatures.length; i++) {
403
404
  if (signatures[i] === null)
404
405
  continue;
@@ -423,13 +424,13 @@ function checkDuplicatePackageRuleMatchers(config, findings) {
423
424
  }
424
425
  const HOST_RULE_SECRET_FIELDS = ["token", "password"];
425
426
  function checkHostRuleInlineSecret(config, findings) {
426
- if (!isPlainObject(config))
427
+ if (!isRecord(config))
427
428
  return;
428
429
  const hostRules = config.hostRules;
429
430
  if (!Array.isArray(hostRules))
430
431
  return;
431
432
  hostRules.forEach((entry, i) => {
432
- if (!isPlainObject(entry))
433
+ if (!isRecord(entry))
433
434
  return;
434
435
  for (const field of HOST_RULE_SECRET_FIELDS) {
435
436
  const value = entry[field];
@@ -450,10 +451,18 @@ function checkHostRuleInlineSecret(config, findings) {
450
451
  }
451
452
  });
452
453
  }
453
- function isPlainObject(v) {
454
- return typeof v === "object" && v !== null && !Array.isArray(v);
455
- }
456
- function makeDeprecatedKeyFinding(oldKey, newKey, path) {
454
+ function makeDeprecatedKeyFinding({ oldKey, newKey }, path) {
455
+ // Plain renames carry the replacement; custom migrations (value coercions,
456
+ // structural rewrites) only tell us the key is deprecated.
457
+ if (newKey === undefined) {
458
+ return {
459
+ ruleId: "deprecated-key",
460
+ severity: "warn",
461
+ path,
462
+ value: oldKey,
463
+ message: `Key '${oldKey}' is deprecated; Renovate migrates it automatically — run migrate_config to see the replacement.`,
464
+ };
465
+ }
457
466
  return {
458
467
  ruleId: "deprecated-key",
459
468
  severity: "warn",
@@ -464,25 +473,24 @@ function makeDeprecatedKeyFinding(oldKey, newKey, path) {
464
473
  };
465
474
  }
466
475
  function checkDeprecatedKeys(config, findings) {
467
- if (!isPlainObject(config))
476
+ if (!isRecord(config))
468
477
  return;
469
478
  for (const key of Object.keys(config)) {
470
- const replacement = DEPRECATED_KEY_LOOKUP.get(key);
471
- if (replacement !== undefined) {
472
- findings.push(makeDeprecatedKeyFinding(key, replacement, key));
473
- }
479
+ const entry = DEPRECATED_KEY_LOOKUP.get(key);
480
+ if (entry)
481
+ findings.push(makeDeprecatedKeyFinding(entry, key));
474
482
  }
475
483
  for (const container of DEPRECATED_KEY_CONTAINERS) {
476
484
  const arr = config[container];
477
485
  if (!Array.isArray(arr))
478
486
  continue;
479
- arr.forEach((entry, i) => {
480
- if (!isPlainObject(entry))
487
+ arr.forEach((item, i) => {
488
+ if (!isRecord(item))
481
489
  return;
482
- for (const key of Object.keys(entry)) {
483
- const replacement = DEPRECATED_KEY_LOOKUP.get(key);
484
- if (replacement !== undefined) {
485
- findings.push(makeDeprecatedKeyFinding(key, replacement, `${container}[${i}].${key}`));
490
+ for (const key of Object.keys(item)) {
491
+ const entry = DEPRECATED_KEY_LOOKUP.get(key);
492
+ if (entry) {
493
+ findings.push(makeDeprecatedKeyFinding(entry, `${container}[${i}].${key}`));
486
494
  }
487
495
  }
488
496
  });
@@ -1,6 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import JSON5 from "json5";
4
+ import { isRecord } from "./util.js";
4
5
  const CANDIDATES = [
5
6
  { path: "renovate.json", format: "json" },
6
7
  { path: "renovate.json5", format: "json5" },
@@ -17,8 +18,11 @@ export async function locateConfig(repoPath) {
17
18
  const abs = path.join(repoPath, candidate.path);
18
19
  try {
19
20
  const raw = await fs.readFile(abs, "utf8");
20
- const config = candidate.format === "json5" ? JSON5.parse(raw) : JSON.parse(raw);
21
- if (!isPlainObject(config)) {
21
+ // Renovate parses every candidate JSONC-then-JSON5 regardless of
22
+ // extension (and write_config round-trips comments into renovate.json);
23
+ // JSON5 is a superset of JSONC, so one parser covers all of them.
24
+ const config = JSON5.parse(raw);
25
+ if (!isRecord(config)) {
22
26
  throw new Error(`${candidate.path} does not contain a JSON object`);
23
27
  }
24
28
  return {
@@ -38,9 +42,9 @@ export async function locateConfig(repoPath) {
38
42
  try {
39
43
  const raw = await fs.readFile(pkgAbs, "utf8");
40
44
  const pkg = JSON.parse(raw);
41
- if (isPlainObject(pkg) &&
45
+ if (isRecord(pkg) &&
42
46
  "renovate" in pkg &&
43
- isPlainObject(pkg.renovate)) {
47
+ isRecord(pkg.renovate)) {
44
48
  return {
45
49
  absPath: pkgAbs,
46
50
  relPath: "package.json",
@@ -56,6 +60,3 @@ export async function locateConfig(repoPath) {
56
60
  }
57
61
  return null;
58
62
  }
59
- function isPlainObject(v) {
60
- return typeof v === "object" && v !== null && !Array.isArray(v);
61
- }