renovate-mcp 1.8.1 → 1.8.2
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.
|
@@ -22049,4 +22049,120 @@ export const PRESETS = {
|
|
|
22049
22049
|
}
|
|
22050
22050
|
}
|
|
22051
22051
|
};
|
|
22052
|
+
/**
|
|
22053
|
+
* Renovate's `removedPresets` map: renamed built-in presets map to their new
|
|
22054
|
+
* name, removed ones map to null (Renovate resolves them to an empty config).
|
|
22055
|
+
*/
|
|
22056
|
+
export const REMOVED_PRESETS = {
|
|
22057
|
+
":app": "config:js-app",
|
|
22058
|
+
":autodetectPinVersions": null,
|
|
22059
|
+
":autodetectRangeStrategy": null,
|
|
22060
|
+
":automergeBranchMergeCommit": ":automergeBranch",
|
|
22061
|
+
":automergeBranchPush": ":automergeBranch",
|
|
22062
|
+
":base": "config:recommended",
|
|
22063
|
+
":disableLockFiles": ":skipArtifactsUpdate",
|
|
22064
|
+
":enableGradleLite": null,
|
|
22065
|
+
":js-app": "config:js-app",
|
|
22066
|
+
":library": "config:js-lib",
|
|
22067
|
+
":masterIssue": ":dependencyDashboard",
|
|
22068
|
+
":masterIssueApproval": ":dependencyDashboardApproval",
|
|
22069
|
+
":switchToGradleLite": null,
|
|
22070
|
+
":unpublishSafe": "npm:unpublishSafe",
|
|
22071
|
+
"compatibility:additionalBranchPrefix": null,
|
|
22072
|
+
"config:application": "config:js-app",
|
|
22073
|
+
"config:base": "config:recommended",
|
|
22074
|
+
"config:base-js": "config:recommended",
|
|
22075
|
+
"config:library": "config:js-lib",
|
|
22076
|
+
"default:app": "config:js-app",
|
|
22077
|
+
"default:automergeBranchMergeCommit": ":automergeBranch",
|
|
22078
|
+
"default:automergeBranchPush": ":automergeBranch",
|
|
22079
|
+
"default:base": "config:recommended",
|
|
22080
|
+
"default:js-app": "config:js-app",
|
|
22081
|
+
"default:library": "config:js-lib",
|
|
22082
|
+
"default:onlyNpm": null,
|
|
22083
|
+
"default:unpublishSafe": "npm:unpublishSafe",
|
|
22084
|
+
"github>whitesource/merge-confidence:beta": "mergeConfidence:all-badges",
|
|
22085
|
+
"group:FontsourceMonorepo": "group:fontsourceMonorepo",
|
|
22086
|
+
"group:HangfireMonorepo": "group:hangfireMonorepo",
|
|
22087
|
+
"group:HotChocolateMonorepo": "group:hotchocolateMonorepo",
|
|
22088
|
+
"group:MassTransitMonorepo": "group:masstransitMonorepo",
|
|
22089
|
+
"group:OpenTelemetryDotnetMonorepo": "group:opentelemetry-dotnetMonorepo",
|
|
22090
|
+
"group:SteeltoeMonorepo": "group:steeltoeMonorepo",
|
|
22091
|
+
"group:SwashbuckleMonorepo": "group:swashbuckle-aspnetcoreMonorepo",
|
|
22092
|
+
"group:System.IO.AbstractionsMonorepo": "group:system.io.abstractionsMonorepo",
|
|
22093
|
+
"group:angular1Monorepo": "group:angularjsMonorepo",
|
|
22094
|
+
"group:angularcliMonorepo": "group:angular-cliMonorepo",
|
|
22095
|
+
"group:arcus background-jobsMonorepo": "group:arcus.background-jobsMonorepo",
|
|
22096
|
+
"group:arcus event-gridMonorepo": "group:arcus.event-gridMonorepo",
|
|
22097
|
+
"group:arcus messagingMonorepo": "group:arcus.messagingMonorepo",
|
|
22098
|
+
"group:arcus observabilityMonorepo": "group:arcus.observabilityMonorepo",
|
|
22099
|
+
"group:arcus securityMonorepo": "group:arcus.securityMonorepo",
|
|
22100
|
+
"group:arcus webapiMonorepo": "group:arcus.webapiMonorepo",
|
|
22101
|
+
"group:aspnet AspNetWebStackMonorepo": "group:aspnet aspnetwebstackMonorepo",
|
|
22102
|
+
"group:aspnet ExtensionsMonorepo": "group:aspnet extensionsMonorepo",
|
|
22103
|
+
"group:hamcrestMonorepo": "group:javahamcrestMonorepo",
|
|
22104
|
+
"group:infrastructureMonorepo": "group:infrastructure-uiMonorepo",
|
|
22105
|
+
"group:jsTestMonMajor": "group:jsTestNonMajor",
|
|
22106
|
+
"group:junit5Monorepo": "group:junit-frameworkMonorepo",
|
|
22107
|
+
"group:k8s-ioMonorepo": "group:kubernetesMonorepo",
|
|
22108
|
+
"group:kubernetes": "group:kubernetesMonorepo",
|
|
22109
|
+
"group:linguiMonorepo": "group:linguijsMonorepo",
|
|
22110
|
+
"group:materialMonorepo": "group:material-components-webMonorepo",
|
|
22111
|
+
"group:muiMonorepo": "group:material-uiMonorepo",
|
|
22112
|
+
"group:nrwlMonorepo": "group:nxMonorepo",
|
|
22113
|
+
"group:openfeignMonorepo": "group:feignMonorepo",
|
|
22114
|
+
"group:opentelemetryMonorepo": "group:opentelemetry-jsMonorepo",
|
|
22115
|
+
"group:picassoMonorepo": "group:picassojsMonorepo",
|
|
22116
|
+
"group:reactrouterMonorepo": "group:react-routerMonorepo",
|
|
22117
|
+
"group:sentryMonorepo": "group:sentry-javascriptMonorepo",
|
|
22118
|
+
"group:strykerMonorepo": "group:stryker-jsMonorepo",
|
|
22119
|
+
"helpers:oddIsUnstable": null,
|
|
22120
|
+
"helpers:oddIsUnstablePackages": null,
|
|
22121
|
+
"monorepo:Fontsource": "monorepo:fontsource",
|
|
22122
|
+
"monorepo:Hangfire": "monorepo:hangfire",
|
|
22123
|
+
"monorepo:HotChocolate": "monorepo:hotchocolate",
|
|
22124
|
+
"monorepo:MassTransit": "monorepo:masstransit",
|
|
22125
|
+
"monorepo:OpenTelemetryDotnet": "monorepo:opentelemetry-dotnet",
|
|
22126
|
+
"monorepo:Steeltoe": "monorepo:steeltoe",
|
|
22127
|
+
"monorepo:Swashbuckle": "monorepo:swashbuckle-aspnetcore",
|
|
22128
|
+
"monorepo:System.IO.Abstractions": "monorepo:system.io.abstractions",
|
|
22129
|
+
"monorepo:angular1": "monorepo:angularjs",
|
|
22130
|
+
"monorepo:angularcli": "monorepo:angular-cli",
|
|
22131
|
+
"monorepo:arcus background-jobs": "monorepo:arcus.background-jobs",
|
|
22132
|
+
"monorepo:arcus event-grid": "monorepo:arcus.event-grid",
|
|
22133
|
+
"monorepo:arcus messaging": "monorepo:arcus.messaging",
|
|
22134
|
+
"monorepo:arcus observability": "monorepo:arcus.observability",
|
|
22135
|
+
"monorepo:arcus security": "monorepo:arcus.security",
|
|
22136
|
+
"monorepo:arcus webapi": "monorepo:arcus.webapi",
|
|
22137
|
+
"monorepo:aspnet AspNetWebStack": "monorepo:aspnet aspnetwebstack",
|
|
22138
|
+
"monorepo:aspnet Extensions": "monorepo:aspnet extensions",
|
|
22139
|
+
"monorepo:hamcrest": "monorepo:javahamcrest",
|
|
22140
|
+
"monorepo:infrastructure": "monorepo:infrastructure-ui",
|
|
22141
|
+
"monorepo:junit5": "monorepo:junit-framework",
|
|
22142
|
+
"monorepo:k8s-io": "monorepo:kubernetes",
|
|
22143
|
+
"monorepo:lingui": "monorepo:linguijs",
|
|
22144
|
+
"monorepo:material": "monorepo:material-components-web",
|
|
22145
|
+
"monorepo:mui": "monorepo:material-ui",
|
|
22146
|
+
"monorepo:nrwl": "monorepo:nx",
|
|
22147
|
+
"monorepo:openfeign": "monorepo:feign",
|
|
22148
|
+
"monorepo:opentelemetry": "monorepo:opentelemetry-js",
|
|
22149
|
+
"monorepo:picasso": "monorepo:picassojs",
|
|
22150
|
+
"monorepo:reactrouter": "monorepo:react-router",
|
|
22151
|
+
"monorepo:sentry": "monorepo:sentry-javascript",
|
|
22152
|
+
"monorepo:stryker": "monorepo:stryker-js",
|
|
22153
|
+
"npm:unpublishSafe": "security:minimumReleaseAgeNpm",
|
|
22154
|
+
"regexManagers:azurePipelinesVersions": "customManagers:azurePipelinesVersions",
|
|
22155
|
+
"regexManagers:biomeVersions": "customManagers:biomeVersions",
|
|
22156
|
+
"regexManagers:bitbucketPipelinesVersions": "customManagers:bitbucketPipelinesVersions",
|
|
22157
|
+
"regexManagers:dockerfileVersions": "customManagers:dockerfileVersions",
|
|
22158
|
+
"regexManagers:githubActionsVersions": "customManagers:githubActionsVersions",
|
|
22159
|
+
"regexManagers:gitlabPipelineVersions": "customManagers:gitlabPipelineVersions",
|
|
22160
|
+
"regexManagers:helmChartYamlAppVersions": "customManagers:helmChartYamlAppVersions",
|
|
22161
|
+
"regexManagers:mavenPropertyVersions": "customManagers:mavenPropertyVersions",
|
|
22162
|
+
"regexManagers:tfvarsVersions": "customManagers:tfvarsVersions",
|
|
22163
|
+
"regexManagers:tsconfigNodeVersions": "customManagers:tsconfigNodeVersions",
|
|
22164
|
+
"replacements:messageFormat-{{package}}-to-@messageformat/{{package}}": "replacements:messageFormat-to-scoped",
|
|
22165
|
+
"replacements:ojdbc10-to-ojdbc11": "replacements:ojdbc-to-ojdbc11",
|
|
22166
|
+
"workarounds:reduceRepologyServerLoad": null
|
|
22167
|
+
};
|
|
22052
22168
|
export const PRESET_NAMES = Object.freeze(Object.keys(PRESETS));
|
|
@@ -60,14 +60,30 @@ function dispatch(parsed, options) {
|
|
|
60
60
|
return Promise.resolve({ ok: false, reason: `Unknown preset source: ${parsed.source}` });
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
/** GitHub, Gitea and Bitbucket address repositories as exactly two path segments (nested groups are a GitLab thing). */
|
|
64
|
+
const OWNER_REPO_RE = /^[^/]+\/[^/]+$/;
|
|
65
|
+
/** GitLab projects live under a namespace that may itself be nested (`group/sub/project`). */
|
|
66
|
+
const GITLAB_PROJECT_RE = /^[^/]+(\/[^/]+)+$/;
|
|
67
|
+
/**
|
|
68
|
+
* Rejects empty, `.` and `..` segments. URL parsing collapses dot segments
|
|
69
|
+
* (also when spelled `%2e`), and `encodeURIComponent` leaves `.` alone, so
|
|
70
|
+
* without this a preset string could steer a token-bearing request to any
|
|
71
|
+
* path on the API host.
|
|
72
|
+
*/
|
|
73
|
+
function isSafePath(path) {
|
|
74
|
+
return path.split("/").every((seg) => seg !== "" && seg !== "." && seg !== "..");
|
|
75
|
+
}
|
|
63
76
|
async function fetchGitHub(parsed, timeoutMs, fetchImpl, endpoint) {
|
|
64
|
-
|
|
77
|
+
const file = presetFileName(parsed);
|
|
78
|
+
if (!parsed.repoPath ||
|
|
79
|
+
!OWNER_REPO_RE.test(parsed.repoPath) ||
|
|
80
|
+
!isSafePath(parsed.repoPath) ||
|
|
81
|
+
!isSafePath(file)) {
|
|
65
82
|
return { ok: false, reason: `Invalid github preset: ${parsed.original}` };
|
|
66
83
|
}
|
|
67
|
-
const file = presetFileName(parsed);
|
|
68
84
|
const ref = parsed.ref ?? "HEAD";
|
|
69
85
|
const apiBase = endpoint ?? DEFAULT_API_BASE.github;
|
|
70
|
-
const url = `${apiBase}/repos/${parsed.repoPath}/contents/${encodeFilePath(file)}?ref=${encodeURIComponent(ref)}`;
|
|
86
|
+
const url = `${apiBase}/repos/${encodeFilePath(parsed.repoPath)}/contents/${encodeFilePath(file)}?ref=${encodeURIComponent(ref)}`;
|
|
71
87
|
const credential = resolveCredential(["RENOVATE_TOKEN", "GITHUB_TOKEN"]);
|
|
72
88
|
const headers = {
|
|
73
89
|
Accept: "application/vnd.github.raw",
|
|
@@ -79,10 +95,13 @@ async function fetchGitHub(parsed, timeoutMs, fetchImpl, endpoint) {
|
|
|
79
95
|
return fetchJson(url, headers, timeoutMs, parsed.original, fetchImpl, "github", sendAuth ? credential : suppressCredential(credential));
|
|
80
96
|
}
|
|
81
97
|
async function fetchGitLab(parsed, timeoutMs, fetchImpl, endpoint) {
|
|
82
|
-
|
|
98
|
+
const file = presetFileName(parsed);
|
|
99
|
+
if (!parsed.repoPath ||
|
|
100
|
+
!GITLAB_PROJECT_RE.test(parsed.repoPath) ||
|
|
101
|
+
!isSafePath(parsed.repoPath) ||
|
|
102
|
+
!isSafePath(file)) {
|
|
83
103
|
return { ok: false, reason: `Invalid gitlab preset: ${parsed.original}` };
|
|
84
104
|
}
|
|
85
|
-
const file = presetFileName(parsed);
|
|
86
105
|
const ref = parsed.ref ?? "HEAD";
|
|
87
106
|
const apiBase = endpoint ?? DEFAULT_API_BASE.gitlab;
|
|
88
107
|
const url = `${apiBase}/projects/${encodeURIComponent(parsed.repoPath)}/repository/files/${encodeURIComponent(file)}/raw?ref=${encodeURIComponent(ref)}`;
|
|
@@ -93,14 +112,15 @@ async function fetchGitLab(parsed, timeoutMs, fetchImpl, endpoint) {
|
|
|
93
112
|
headers["PRIVATE-TOKEN"] = credential.token;
|
|
94
113
|
return fetchJson(url, headers, timeoutMs, parsed.original, fetchImpl, "gitlab", sendAuth ? credential : suppressCredential(credential));
|
|
95
114
|
}
|
|
96
|
-
/** Gitea and Bitbucket address repositories as exactly two path segments (nested groups are a GitLab thing). */
|
|
97
|
-
const OWNER_REPO_RE = /^[^/]+\/[^/]+$/;
|
|
98
115
|
/** Gitea's raw-contents API, which Forgejo serves unchanged; the two differ only in default host and token var. */
|
|
99
116
|
async function fetchGitea(parsed, timeoutMs, fetchImpl, endpoint, platform) {
|
|
100
|
-
|
|
117
|
+
const file = presetFileName(parsed);
|
|
118
|
+
if (!parsed.repoPath ||
|
|
119
|
+
!OWNER_REPO_RE.test(parsed.repoPath) ||
|
|
120
|
+
!isSafePath(parsed.repoPath) ||
|
|
121
|
+
!isSafePath(file)) {
|
|
101
122
|
return { ok: false, reason: `Invalid ${platform} preset: ${parsed.original}` };
|
|
102
123
|
}
|
|
103
|
-
const file = presetFileName(parsed);
|
|
104
124
|
const apiBase = endpoint ?? DEFAULT_API_BASE[platform];
|
|
105
125
|
// No `ref` means the repository's default branch, as in Renovate.
|
|
106
126
|
const refQuery = parsed.ref ? `?ref=${encodeURIComponent(parsed.ref)}` : "";
|
|
@@ -124,12 +144,21 @@ async function fetchGitea(parsed, timeoutMs, fetchImpl, endpoint, platform) {
|
|
|
124
144
|
* if anyone needs it.
|
|
125
145
|
*/
|
|
126
146
|
async function fetchBitbucket(parsed, timeoutMs, fetchImpl, endpoint) {
|
|
127
|
-
|
|
147
|
+
const file = presetFileName(parsed);
|
|
148
|
+
// The ref is a single encoded path segment here (a query parameter elsewhere), so only an
|
|
149
|
+
// empty or dot ref can shape the path.
|
|
150
|
+
const rawRef = parsed.ref ?? "HEAD";
|
|
151
|
+
if (!parsed.repoPath ||
|
|
152
|
+
!OWNER_REPO_RE.test(parsed.repoPath) ||
|
|
153
|
+
!isSafePath(parsed.repoPath) ||
|
|
154
|
+
!isSafePath(file) ||
|
|
155
|
+
rawRef === "" ||
|
|
156
|
+
rawRef === "." ||
|
|
157
|
+
rawRef === "..") {
|
|
128
158
|
return { ok: false, reason: `Invalid bitbucket preset: ${parsed.original}` };
|
|
129
159
|
}
|
|
130
|
-
const file = presetFileName(parsed);
|
|
131
160
|
const apiBase = endpoint ?? DEFAULT_API_BASE.bitbucket;
|
|
132
|
-
const ref = encodeURIComponent(
|
|
161
|
+
const ref = encodeURIComponent(rawRef);
|
|
133
162
|
const url = `${apiBase}/2.0/repositories/${encodeFilePath(parsed.repoPath)}/src/${ref}/${encodeFilePath(file)}`;
|
|
134
163
|
const credential = resolveCredential(["RENOVATE_TOKEN", "BITBUCKET_TOKEN"]);
|
|
135
164
|
const headers = { "User-Agent": "renovate-mcp" };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PRESETS } from "../data/presets.generated.js";
|
|
1
|
+
import { PRESETS, REMOVED_PRESETS } from "../data/presets.generated.js";
|
|
2
2
|
import { fetchExternalPreset } from "./externalPresetFetcher.js";
|
|
3
3
|
import { HOSTED_PLATFORMS } from "./gitRemote.js";
|
|
4
4
|
import { runMerge } from "./mergeWorker.js";
|
|
@@ -130,7 +130,29 @@ async function collectSteps(input, ownerName, viaChain, ctx) {
|
|
|
130
130
|
});
|
|
131
131
|
continue;
|
|
132
132
|
}
|
|
133
|
-
const
|
|
133
|
+
const redirect = followRemovedPresets(entry);
|
|
134
|
+
if (redirect.kind === "cycle") {
|
|
135
|
+
ctx.presetsUnresolved.push({
|
|
136
|
+
preset: entry,
|
|
137
|
+
reason: `Cycle detected in Renovate's removed/renamed preset map: ${redirect.chain.join(" → ")}`,
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (redirect.kind === "removed") {
|
|
142
|
+
ctx.warnings.push({
|
|
143
|
+
preset: entry,
|
|
144
|
+
message: `Preset "${entry}" was removed in Renovate and is ignored (it resolves to nothing); remove it from \`extends\` (migrate_config does this).`,
|
|
145
|
+
});
|
|
146
|
+
ctx.presetsResolved.push(entry);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const renamedTo = redirect.kind === "renamed" ? redirect.target : null;
|
|
150
|
+
// The target carries no args, so the original entry's args never apply
|
|
151
|
+
// (same as Renovate). `original` stays what the user wrote so unresolved
|
|
152
|
+
// records and labels name the entry as written.
|
|
153
|
+
const parsed = renamedTo === null
|
|
154
|
+
? parsePreset(entry)
|
|
155
|
+
: { ...parsePreset(renamedTo), original: entry };
|
|
134
156
|
if (ctx.stack.includes(parsed.key)) {
|
|
135
157
|
ctx.presetsUnresolved.push({
|
|
136
158
|
preset: entry,
|
|
@@ -138,7 +160,21 @@ async function collectSteps(input, ownerName, viaChain, ctx) {
|
|
|
138
160
|
});
|
|
139
161
|
continue;
|
|
140
162
|
}
|
|
163
|
+
const unresolvedBefore = ctx.presetsUnresolved.length;
|
|
141
164
|
const body = await loadPresetBody(parsed, ctx);
|
|
165
|
+
if (renamedTo !== null) {
|
|
166
|
+
if (!body) {
|
|
167
|
+
for (const u of ctx.presetsUnresolved.slice(unresolvedBefore)) {
|
|
168
|
+
u.reason += ` (Renovate renamed "${entry}" to "${renamedTo}".)`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
ctx.warnings.push({
|
|
173
|
+
preset: entry,
|
|
174
|
+
message: `Preset "${entry}" was renamed in Renovate to "${renamedTo}" and was resolved as that preset; update \`extends\` (migrate_config does this).`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
142
178
|
if (!body)
|
|
143
179
|
continue;
|
|
144
180
|
const { value, missingArgs, unknownTemplates } = applyArgs(body, parsed.args);
|
|
@@ -160,6 +196,30 @@ async function collectSteps(input, ownerName, viaChain, ctx) {
|
|
|
160
196
|
});
|
|
161
197
|
}
|
|
162
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Follow Renovate's `removedPresets` map on the exact raw entry (args included,
|
|
201
|
+
* no trimming), repeatedly like Renovate's recursive lookup. A string value is a
|
|
202
|
+
* rename, null means Renovate drops the preset. Bounded by a visited set.
|
|
203
|
+
*/
|
|
204
|
+
function followRemovedPresets(entry) {
|
|
205
|
+
const lookup = (name) => Object.hasOwn(REMOVED_PRESETS, name) ? REMOVED_PRESETS[name] : undefined;
|
|
206
|
+
const seen = [entry];
|
|
207
|
+
let current = entry;
|
|
208
|
+
for (;;) {
|
|
209
|
+
const next = lookup(current);
|
|
210
|
+
// Renovate treats an empty-string value as falsy, i.e. not a redirect.
|
|
211
|
+
if (next === undefined || next === "") {
|
|
212
|
+
return current === entry ? { kind: "none" } : { kind: "renamed", target: current };
|
|
213
|
+
}
|
|
214
|
+
if (next === null) {
|
|
215
|
+
return { kind: "removed" };
|
|
216
|
+
}
|
|
217
|
+
if (seen.includes(next))
|
|
218
|
+
return { kind: "cycle", chain: [...seen, next] };
|
|
219
|
+
seen.push(next);
|
|
220
|
+
current = next;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
163
223
|
export function recordTemplateWarnings(entry, suppliedArgs, missingArgs, unknownTemplates, warningsList) {
|
|
164
224
|
for (const idx of [...missingArgs].sort((a, b) => a - b)) {
|
|
165
225
|
warningsList.push({
|
|
@@ -203,7 +263,13 @@ export async function loadPresetBody(parsed, ctx) {
|
|
|
203
263
|
: parsed;
|
|
204
264
|
const classification = classifyExternalSource(effective.source);
|
|
205
265
|
if (!classification.fetchable) {
|
|
206
|
-
|
|
266
|
+
const bareNote = parsed.source === "local" && !/^local>/i.test(parsed.original.trim())
|
|
267
|
+
? ` Renovate treats a prefix-less \`owner/repo\` entry as \`local>owner/repo\`.`
|
|
268
|
+
: "";
|
|
269
|
+
unresolvedList.push({
|
|
270
|
+
preset: parsed.original,
|
|
271
|
+
reason: classification.reason + bareNote,
|
|
272
|
+
});
|
|
207
273
|
return null;
|
|
208
274
|
}
|
|
209
275
|
if (!ctx.fetchExternal) {
|
|
@@ -235,6 +301,35 @@ const KNOWN_SOURCES = new Set([
|
|
|
235
301
|
"local",
|
|
236
302
|
"npm",
|
|
237
303
|
]);
|
|
304
|
+
// Renovate's internal preset namespaces, verbatim from its parser
|
|
305
|
+
// (`config/presets/parse.ts`). It checks these before treating a
|
|
306
|
+
// slash-containing entry as `local>`, so e.g. `group:foo/bar` stays a built-in
|
|
307
|
+
// lookup. Hard-coded rather than derived from the catalogue: `compatibility`
|
|
308
|
+
// and `npm` have no presets but are still internal to the parser.
|
|
309
|
+
const BUILTIN_NAMESPACES = new Set([
|
|
310
|
+
"abandonments",
|
|
311
|
+
"compatibility",
|
|
312
|
+
"config",
|
|
313
|
+
"customManagers",
|
|
314
|
+
"default",
|
|
315
|
+
"docker",
|
|
316
|
+
"global",
|
|
317
|
+
"group",
|
|
318
|
+
"helpers",
|
|
319
|
+
"mergeConfidence",
|
|
320
|
+
"monorepo",
|
|
321
|
+
"npm",
|
|
322
|
+
"packages",
|
|
323
|
+
"preview",
|
|
324
|
+
"replacements",
|
|
325
|
+
"schedule",
|
|
326
|
+
"security",
|
|
327
|
+
"workarounds",
|
|
328
|
+
]);
|
|
329
|
+
/** True when Renovate would treat `entry` as a `local>` preset (prefixed or bare `owner/repo`). */
|
|
330
|
+
export function isLocalPreset(entry) {
|
|
331
|
+
return parsePreset(entry).source === "local";
|
|
332
|
+
}
|
|
238
333
|
export function parsePreset(raw) {
|
|
239
334
|
const original = raw;
|
|
240
335
|
let rest = raw.trim();
|
|
@@ -252,9 +347,39 @@ export function parsePreset(raw) {
|
|
|
252
347
|
if (srcMatch) {
|
|
253
348
|
return parseExternal(rest, original, args, srcMatch[1].toLowerCase());
|
|
254
349
|
}
|
|
350
|
+
// Entries without a `<source>>` prefix follow Renovate's preset parser for
|
|
351
|
+
// the source split: relative path, then http(s) URL, then `:`-prefixed
|
|
352
|
+
// built-in, then `@scope/pkg` npm, then anything else containing `/` (outside
|
|
353
|
+
// a built-in namespace) as a `local>` preset. The rest deliberately differs:
|
|
354
|
+
// any other `foo:bar` is looked up as a built-in (Renovate would try the npm
|
|
355
|
+
// package `renovate-config-foo`), which gives a clearer typo message.
|
|
356
|
+
if (/^\.{0,2}\//.test(rest)) {
|
|
357
|
+
return {
|
|
358
|
+
key: original,
|
|
359
|
+
original,
|
|
360
|
+
args,
|
|
361
|
+
unresolvableReason: "Relative presets are not supported by resolve_config; run dry_run for full-fidelity resolution.",
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (/^https?:\/\//i.test(rest)) {
|
|
365
|
+
return {
|
|
366
|
+
key: original,
|
|
367
|
+
original,
|
|
368
|
+
args,
|
|
369
|
+
unresolvableReason: "URL (http) presets are not supported by resolve_config; run dry_run for full-fidelity resolution.",
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (!rest.startsWith(":")) {
|
|
373
|
+
if (rest.startsWith("@")) {
|
|
374
|
+
return parseExternal(`npm>${rest}`, original, args, "npm");
|
|
375
|
+
}
|
|
376
|
+
if (rest.includes("/") && !BUILTIN_NAMESPACES.has(rest.split(":", 1)[0])) {
|
|
377
|
+
return parseExternal(`local>${rest}`, original, args, "local");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
255
380
|
// Built-in: `:foo` is shorthand for `default:foo`.
|
|
256
381
|
const key = rest.startsWith(":") ? `default${rest}` : rest;
|
|
257
|
-
//
|
|
382
|
+
// Otherwise an entry with no `:` namespace is an npm preset.
|
|
258
383
|
if (!key.includes(":")) {
|
|
259
384
|
return {
|
|
260
385
|
key: `npm>${key}`,
|
package/dist/lib/version.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
-
export const SERVER_VERSION = "1.8.
|
|
2
|
+
export const SERVER_VERSION = "1.8.2"; // 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"]}`
|
package/dist/tools/dryRun.js
CHANGED
|
@@ -6,6 +6,7 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { run, resolveRenovateTool, formatMissingBinaryError, formatTimeoutError, CommandTimeoutError, } from "../lib/renovateCli.js";
|
|
7
7
|
import { resolveCredential } from "../lib/credentialResolver.js";
|
|
8
8
|
import { validateEndpoint } from "../lib/endpointValidator.js";
|
|
9
|
+
import { isLocalPreset } from "../lib/presetResolver.js";
|
|
9
10
|
import { locateConfig } from "../lib/configLocations.js";
|
|
10
11
|
import { assertRepoDir, NETWORK_READ_ONLY_ANNOTATIONS } from "../lib/toolInputs.js";
|
|
11
12
|
import { detectLookupProblems } from "../lib/lookupProblems.js";
|
|
@@ -80,7 +81,8 @@ function collectReportProblems(report) {
|
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Scan the repo's Renovate config for `extends` entries of the form
|
|
83
|
-
* `local>owner/repo[:preset]`
|
|
84
|
+
* `local>owner/repo[:preset]` (or the bare `owner/repo[:preset]` spelling
|
|
85
|
+
* Renovate also treats as local) and return them along with the config-file path
|
|
84
86
|
* they were found in. Returns `null` if no config is found (Renovate will
|
|
85
87
|
* handle that case itself with its usual error). Silently returns `null` on
|
|
86
88
|
* parse errors too — we don't want preflight to be stricter than Renovate's
|
|
@@ -99,7 +101,7 @@ async function detectUnresolvableLocalPresets(repoPath) {
|
|
|
99
101
|
const extendsRaw = located.config.extends;
|
|
100
102
|
if (!Array.isArray(extendsRaw))
|
|
101
103
|
return null;
|
|
102
|
-
const presets = extendsRaw.filter((entry) => typeof entry === "string" && entry
|
|
104
|
+
const presets = extendsRaw.filter((entry) => typeof entry === "string" && isLocalPreset(entry));
|
|
103
105
|
return { relPath: located.relPath, presets };
|
|
104
106
|
}
|
|
105
107
|
function countRepositories(report) {
|
|
@@ -137,8 +137,41 @@ export function registerWriteConfig(server) {
|
|
|
137
137
|
? `${target}.renovate-mcp-slice-${randomUUID()}.json`
|
|
138
138
|
: undefined;
|
|
139
139
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
140
|
-
|
|
140
|
+
// rename() keeps tmp's inode, so tmp's mode becomes the target's. Capture
|
|
141
|
+
// an existing regular file's permission bits so an overwrite does not
|
|
142
|
+
// silently tighten e.g. 0644 to 0600. `stat` (not `lstat`): `target` is
|
|
143
|
+
// already a realpath, and a dangling link must read as ENOENT (new file).
|
|
144
|
+
// A concurrent chmod between this stat and the rename is lost; benign.
|
|
145
|
+
let targetMode = undefined;
|
|
141
146
|
try {
|
|
147
|
+
const st = await fs.stat(target);
|
|
148
|
+
if (st.isFile())
|
|
149
|
+
targetMode = st.mode & 0o777;
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.code !== "ENOENT")
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
// Create tmp 0o600 when copying a mode over it (owner-only until the
|
|
156
|
+
// fchmod below); for a new file ask for 0o666 so the kernel applies the
|
|
157
|
+
// process umask, avoiding the deprecated no-arg process.umask(). While the
|
|
158
|
+
// validator runs, tmp is as readable as the written file would be (also
|
|
159
|
+
// when validation then refuses the write); it never exceeds that. The
|
|
160
|
+
// mode is set through the open handle (fchmod), never `fs.chmod(tmp)`,
|
|
161
|
+
// which would re-resolve the path and follow a symlink swapped in at
|
|
162
|
+
// that name. The open stays outside the
|
|
163
|
+
// try: on EEXIST the entry at tmp is not ours, so `finally` must not
|
|
164
|
+
// unlink it.
|
|
165
|
+
const fh = await fs.open(tmp, "wx", targetMode === undefined ? 0o666 : 0o600);
|
|
166
|
+
try {
|
|
167
|
+
try {
|
|
168
|
+
await fh.writeFile(payload);
|
|
169
|
+
if (targetMode !== undefined)
|
|
170
|
+
await fh.chmod(targetMode);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
await fh.close();
|
|
174
|
+
}
|
|
142
175
|
if (sliceTmp) {
|
|
143
176
|
await fs.writeFile(sliceTmp, JSON.stringify(config, null, 2) + "\n", {
|
|
144
177
|
flag: "wx",
|