Files
Sebastian Poxhofer 8d08b51db0 refactor: return a RepositoryResult while onboarding (#45531)
* refactor(onboarding): return repository result from ensureOnboarding

Rename ensureOnboardingPr to ensureOnboarding and have it return a new
'onboarding' RepositoryResult. renovateRepository now branches on
repoIsOnboarded, so exactly one of ensureOnboarding/updateRepo produces
the repository result and the non-null assertion on the update result
(TODO #22198) is no longer needed. updateRepo/update no longer return
undefined for non-onboarded repositories.

* refactor(onboarding): keep ensureOnboardingPr name
2026-08-28 11:50:28 +00:00

264 lines
8.2 KiB
TypeScript

// TODO #22198
import { GlobalConfig } from '../../../config/global.ts';
import { mergeChildConfig } from '../../../config/index.ts';
import { migrateAndValidate } from '../../../config/migrate-validate.ts';
import { resolveConfigPresets } from '../../../config/presets/index.ts';
import type { RenovateConfig } from '../../../config/types.ts';
import { CONFIG_VALIDATION } from '../../../constants/error-messages.ts';
import { instrument } from '../../../instrumentation/index.ts';
import { ATTR_RENOVATE_SPLIT } from '../../../instrumentation/types.ts';
import { addMeta, logger, removeMeta } from '../../../logger/index.ts';
import type { PackageFile } from '../../../modules/manager/types.ts';
import { platform } from '../../../modules/platform/index.ts';
import { scm } from '../../../modules/platform/scm.ts';
import { getCache } from '../../../util/cache/repository/index.ts';
import { clone } from '../../../util/clone.ts';
import { getBranchList } from '../../../util/git/index.ts';
import { addSplit } from '../../../util/split.ts';
import { getRegexPredicate } from '../../../util/string-match.ts';
import type { BranchConfig } from '../../types.ts';
import { readDashboardBody } from '../dependency-dashboard.ts';
import type { ExtractResult } from './extract-update.ts';
import { extract, lookup, update } from './extract-update.ts';
import type { WriteUpdateResult } from './write.ts';
export async function getBaseBranchConfig(
baseBranch: string,
config: RenovateConfig,
): Promise<RenovateConfig> {
logger.debug(`baseBranch: ${baseBranch}`);
let baseBranchConfig: RenovateConfig = clone(config);
if (
config.useBaseBranchConfig === 'merge' &&
baseBranch !== config.defaultBranch
) {
logger.debug(
{ baseBranch },
`Merging config from base branch because useBaseBranchConfig=merge`,
);
// Retrieve config file name autodetected for this repo
const cache = getCache();
// TODO: types (#22198)
const configFileName = cache.configFileName!;
try {
baseBranchConfig = await platform.getJsonFile(
configFileName,
config.repository,
baseBranch,
);
logger.debug({ config: baseBranchConfig }, 'Base branch config raw');
} catch {
logger.error(
{ configFileName, baseBranch },
`Error fetching config file from base branch - possible config name mismatch between branches?`,
);
const error = new Error(CONFIG_VALIDATION);
error.validationSource = 'config';
error.validationError = 'Error fetching config file';
error.validationMessage = `Error fetching config file \`${configFileName}\` from branch \`${baseBranch}\``;
throw error;
}
baseBranchConfig = await migrateAndValidate(config, baseBranchConfig);
if (baseBranchConfig.errors?.length) {
const error = new Error(CONFIG_VALIDATION);
error.validationSource = configFileName;
error.validationError = `The renovate configuration file of branch ${baseBranch} contains some invalid settings`;
error.validationMessage = baseBranchConfig.errors
.map((e) => e.message)
.join(', ');
throw error;
}
({ config: baseBranchConfig } = await resolveConfigPresets(
baseBranchConfig,
config,
));
baseBranchConfig = mergeChildConfig(config, baseBranchConfig);
// istanbul ignore if
if (config.printConfig) {
logger.info(
{ config: baseBranchConfig },
'Base branch config after merge',
);
}
// baseBranches value should be based off the default branch
baseBranchConfig.baseBranchPatterns = config.baseBranchPatterns;
baseBranchConfig.baseBranches = config.baseBranches;
}
if (isMultiBaseBranch(config)) {
baseBranchConfig.branchPrefix += `${baseBranch}-`;
baseBranchConfig.hasBaseBranches = true;
}
baseBranchConfig = mergeChildConfig(baseBranchConfig, { baseBranch });
return baseBranchConfig;
}
function unfoldBaseBranches(
defaultBranch: string,
baseBranchPatterns: string[],
): string[] {
const unfoldedList: string[] = [];
const allBranches = getBranchList();
for (const baseBranchPattern of baseBranchPatterns) {
const isAllowedPred = getRegexPredicate(baseBranchPattern);
if (isAllowedPred) {
const matchingBranches = allBranches.filter(isAllowedPred);
logger.debug(
`baseBranchePatterns regex "${baseBranchPattern}" matches [${matchingBranches.join()}]`,
);
unfoldedList.push(...matchingBranches);
} else if (baseBranchPattern === '$default') {
logger.debug(`baseBranchPatterns "$default" matches "${defaultBranch}"`);
unfoldedList.push(defaultBranch);
} else {
unfoldedList.push(baseBranchPattern);
}
}
return [...new Set(unfoldedList)];
}
export function isMultiBaseBranch(config: RenovateConfig): boolean {
if (!config.baseBranchPatterns?.length) {
return false;
}
return (
config.baseBranchPatterns.length > 1 ||
config.baseBranchPatterns[0].startsWith('/')
);
}
export async function extractDependencies(
config: RenovateConfig,
overwriteCache = true,
): Promise<ExtractResult> {
await readDashboardBody(config);
let res: ExtractResult = {
branches: [],
branchList: [],
packageFiles: {},
};
if (
GlobalConfig.get('platform') !== 'local' &&
config.baseBranchPatterns?.length
) {
config.baseBranches = unfoldBaseBranches(
config.defaultBranch!,
config.baseBranchPatterns,
);
logger.debug({ baseBranches: config.baseBranches }, 'baseBranches');
const extracted: Record<string, Record<string, PackageFile[]>> = {};
await instrument(
'extract',
async () => {
for (const baseBranch of config.baseBranches!) {
addMeta({ baseBranch });
if (scm.syncForkWithUpstream) {
await scm.syncForkWithUpstream(baseBranch);
}
if (await scm.branchExists(baseBranch)) {
const baseBranchConfig = await getBaseBranchConfig(
baseBranch,
config,
);
extracted[baseBranch] = await extract(
baseBranchConfig,
overwriteCache,
);
} else {
logger.warn(
{ baseBranch },
'Base branch does not exist - skipping',
);
}
}
},
{
attributes: {
[ATTR_RENOVATE_SPLIT]: 'extract',
},
},
);
addSplit('extract');
await instrument(
'lookup',
async () => {
for (const baseBranch of config.baseBranches!) {
if (await scm.branchExists(baseBranch)) {
addMeta({ baseBranch });
const baseBranchConfig = await getBaseBranchConfig(
baseBranch,
config,
);
const packageFiles = extracted[baseBranch];
const baseBranchRes = await lookup(baseBranchConfig, packageFiles);
res.branches = res.branches.concat(baseBranchRes?.branches);
res.branchList = res.branchList.concat(baseBranchRes?.branchList);
if (!res.packageFiles || !Object.keys(res.packageFiles).length) {
// Use the first branch
res.packageFiles = baseBranchRes?.packageFiles;
}
}
}
},
{
attributes: {
[ATTR_RENOVATE_SPLIT]: 'lookup',
},
},
);
removeMeta(['baseBranch']);
} else {
logger.debug('No baseBranches');
const packageFiles = await instrument(
'extract',
async () => await extract(config, overwriteCache),
{
attributes: {
[ATTR_RENOVATE_SPLIT]: 'extract',
},
},
);
addSplit('extract');
if (GlobalConfig.get('dryRun') === 'extract') {
res.packageFiles = packageFiles;
logger.info({ packageFiles }, 'Extracted dependencies');
return res;
}
res = await instrument(
'lookup',
async () => await lookup(config, packageFiles),
{
attributes: {
[ATTR_RENOVATE_SPLIT]: 'lookup',
},
},
);
}
addSplit('lookup');
return res;
}
export function updateRepo(
config: RenovateConfig,
branches: BranchConfig[],
): Promise<WriteUpdateResult> {
logger.debug('processRepo()');
return update(config, branches);
}