Files
Alex Thomae f3492fe1c8 fix(workers/repository): use GlobalConfig for productLinks (#44126)
* fix(workers/repository): use GlobalConfig for productLinks

PR #44098 added productLinks to GlobalConfig.OPTIONS, which moves it into
GlobalConfig and deletes it from the per-repo config. Several readers
were not migrated and now receive undefined from config.productLinks.

Most visibly, setStability and setConfidence build the status check
target_url from config.productLinks?.documentation. With it now empty the
URL becomes scheme-relative (e.g. "key-concepts/minimum-release-age/"),
which GitLab rejects with HTTP 400. That throws in setBranchStatus and
aborts the branch before its merge request is created.

Migrate the remaining readers to GlobalConfig.get('productLinks'),
matching #44098, and drop the now-redundant coerceString calls and
non-null assertions.

* refactor(config): drop productLinks from RenovateSharedConfig

productLinks is globalOnly and is now read via GlobalConfig.get. Leaving
it on the per-repo RenovateSharedConfig let callers read the (always
empty) config.productLinks, which is the footgun this PR fixes. Remove it
from the shared interface so it only exists on RepoGlobalConfig, and the
type now prevents reintroducing config.productLinks.
2026-06-22 07:16:59 +00:00

136 lines
4.8 KiB
TypeScript

import { isString } from '@sindresorhus/is';
import { GlobalConfig } from '../../../../config/global.ts';
import type { RenovateConfig } from '../../../../config/types.ts';
import { logger } from '../../../../logger/index.ts';
import type { Pr } from '../../../../modules/platform/index.ts';
import { platform } from '../../../../modules/platform/index.ts';
import { hashBody } from '../../../../modules/platform/pr-body.ts';
import { scm } from '../../../../modules/platform/scm.ts';
import { emojify } from '../../../../util/emoji.ts';
import * as template from '../../../../util/template/index.ts';
import { joinUrlParts } from '../../../../util/url.ts';
import { getPlatformPrOptions } from '../../update/pr/index.ts';
import { prepareLabels } from '../../update/pr/labels.ts';
import { addParticipants } from '../../update/pr/participants.ts';
import { ConfigMigrationCommitMessageFactory } from '../branch/commit-message.ts';
import type { MigratedData } from '../branch/migrated-data.ts';
import { getMigrationBranchName } from '../common.ts';
export async function ensureConfigMigrationPr(
config: RenovateConfig,
migratedConfigData: MigratedData,
): Promise<Pr | null> {
logger.debug('ensureConfigMigrationPr()');
const docsLink = joinUrlParts(
GlobalConfig.get('productLinks').documentation,
'configuration-options/#configmigration',
);
const branchName = getMigrationBranchName(config);
const commitMessageFactory = new ConfigMigrationCommitMessageFactory(
config,
migratedConfigData.filename,
);
const prTitle = commitMessageFactory.getPrTitle();
const existingPr = await platform.getBranchPr(branchName, config.baseBranch);
const filename = migratedConfigData.filename;
logger.debug('Filling in config migration PR template');
let prBody = `The Renovate config in this repository needs migrating. Typically this is because one or more configuration options you are using have been renamed.
You don't need to merge this PR right away, because Renovate will continue to migrate these fields internally each time it runs. But later some of these fields may be fully deprecated and the migrations removed. So it's a good idea to merge this migration PR soon. \n\n`;
prBody += emojify(
`
${
filename.endsWith('.json5')
? `#### [PLEASE NOTE](${docsLink}): ` +
`JSON5 config file migrated! All comments & trailing commas were removed.`
: ''
}
:no_bell: **Ignore**: Close this PR and you won't be reminded about config migration again, but one day your current config may no longer be valid.
:question: Got questions? Does something look wrong to you? Please don't hesitate to [request help here](${
GlobalConfig.get('productLinks').help
}).\n\n`,
);
if (isString(config.prHeader)) {
prBody = `${template.compile(config.prHeader, config)}\n\n${prBody}`;
}
if (isString(config.prFooter)) {
prBody = `${prBody}\n---\n\n${template.compile(config.prFooter, config)}\n`;
}
logger.trace({ prBody }, 'prBody');
prBody = platform.massageMarkdown(prBody, config.rebaseLabel);
if (existingPr) {
logger.debug('Found open migration PR');
// Check if existing PR needs updating
const prBodyHash = hashBody(prBody);
if (
existingPr.bodyStruct?.hash === prBodyHash &&
existingPr.title === prTitle
) {
logger.debug(`Pr does not need updating, PrNo: ${existingPr.number}`);
return existingPr;
}
// PR must need updating
if (GlobalConfig.get('dryRun')) {
logger.info('DRY-RUN: Would update migration PR');
} else {
await platform.updatePr({
number: existingPr.number,
prTitle,
prBody,
});
logger.info({ pr: existingPr.number }, 'Migration PR updated');
}
return existingPr;
}
logger.debug('Creating migration PR');
const labels = prepareLabels(config);
try {
if (GlobalConfig.get('dryRun')) {
logger.info('DRY-RUN: Would create migration PR');
} else {
const pr = await platform.createPr({
sourceBranch: branchName,
// TODO #22198
targetBranch: config.defaultBranch!,
prTitle,
prBody,
labels,
platformPrOptions: getPlatformPrOptions({
...config,
automerge: false,
}),
});
logger.info({ pr: pr?.number }, 'Migration PR created');
if (pr) {
await addParticipants(config, pr);
}
return pr;
}
} catch (err) {
if (
err.response?.statusCode === 422 &&
err.response?.body?.errors?.[0]?.message?.startsWith(
'A pull request already exists',
)
) {
logger.warn(
{ err },
'Migration PR already exists but cannot find it. It was probably created by a different user.',
);
await scm.deleteBranch(branchName);
return null;
}
throw err;
}
return null;
}