Files
Sebastian Poxhofer 97327c3d28 refactor(linters): add prefer-coerce-array rule (#45492)
* refactor(linters): add prefer-coerce-array rule

Refs #45217

`x ?? []` and `coerceArray(x)` say the same thing, but only the second
one is searchable and consistent. The rule flags a `??` whose default is
a bare `[]` literal; a non-empty default or an asserted `([] as Foo[])`
still passes.

Applying it across `lib/` surfaced three spots the helper cannot cover,
which keep `?? []` with a disable comment or a narrower fix:

- `package-files.ts` destructures a tuple, which `coerceArray()` widens
- `generate.spec.ts` defaults `matchAll()`, an iterator rather than an array
- `mise/upgradeable-tooling.ts` declares a `Record` return, so its
  `?? []` fallback was wrong to begin with and becomes `coerceObject()`

* refactor(platform/github): hoist the GHE version header lookup

`Object.entries(...).find()` returns a tuple, and defaulting it with
`?? []` only to destructure the second element reads as an array
default when it is not one. Name the found entry instead, which drops
the `?? []` rather than routing a tuple through `coerceArray()`.
2026-08-28 09:27:58 +00:00

144 lines
4.7 KiB
TypeScript

import { DateTime } from 'luxon';
import type { RenovateConfig } from '../../../config/types.ts';
import { instrument } from '../../../instrumentation/index.ts';
import { logger } from '../../../logger/index.ts';
import { platform } from '../../../modules/platform/index.ts';
import { scm } from '../../../modules/platform/scm.ts';
import { ExternalHostError } from '../../../types/errors/external-host-error.ts';
import { coerceArray } from '../../../util/array.ts';
import { getCache } from '../../../util/cache/repository/index.ts';
import { getInheritedOrGlobal } from '../../../util/common.ts';
import type { BranchConfig } from '../../types.ts';
export async function getPrHourlyCount(
config: RenovateConfig,
): Promise<number> {
return await instrument('getPrHourlyCount', async () => {
try {
const prList = await platform.getPrList();
const currentHourStart = DateTime.utc().startOf('hour');
logger.debug(
`Calculating PRs created so far in this hour currentHourStart=${String(currentHourStart)}`,
);
const soFarThisHour = prList.filter(
(pr) =>
pr.sourceBranch !== getInheritedOrGlobal('onboardingBranch') &&
pr.sourceBranch.startsWith(config.branchPrefix!) &&
DateTime.fromISO(pr.createdAt!).toUTC() > currentHourStart,
);
logger.debug(
`${soFarThisHour.length} PRs have been created so far in this hour.`,
);
return soFarThisHour.length;
} catch (err) {
// istanbul ignore if
if (err instanceof ExternalHostError) {
throw err;
}
logger.error({ err }, 'Error checking PRs created per hour');
return 0;
}
});
}
export async function getConcurrentPrsCount(
config: RenovateConfig,
branches: BranchConfig[],
): Promise<number> {
return await instrument('getConcurrentPrsCount', async () => {
let openPrCount = 0;
for (const { branchName } of branches) {
try {
const pr = await platform.getBranchPr(branchName, config.baseBranch);
if (
pr &&
pr.sourceBranch !== getInheritedOrGlobal('onboardingBranch') &&
pr.state === 'open'
) {
openPrCount++;
}
} catch (err) {
// istanbul ignore if
if (err instanceof ExternalHostError) {
throw err;
} else {
// no-op
}
}
}
logger.debug(`${openPrCount} PRs are currently open`);
return openPrCount;
});
}
export async function getCommitsHourlyCount(
branches: BranchConfig[],
): Promise<number> {
return await instrument('getCommitsHourlyCount', async () => {
try {
const currentHourStart = DateTime.utc().startOf('hour');
logger.debug(
`Calculating commits so far in this hour currentHourStart=${String(currentHourStart)}, for ${branches.length} branches`,
);
const cache = getCache();
const cachedBranches = coerceArray(cache.branches);
// if we don't have all of our branches in our cache (for instance, if we're not using the Repository Cache, or this is the first run against a repo), we need to fall back to the SCM
const needsScmFallback = branches.some(
(branch) =>
!cachedBranches.find((b) => b.branchName === branch.branchName)
?.commitTimestamp,
);
const fallbackUpdateDates = needsScmFallback
? await scm.getAllBranchUpdateDates()
: {};
let soFarThisHour = 0;
for (const branch of branches) {
// First try to get from cache
const branchCache = cachedBranches.find(
(b) => b.branchName === branch.branchName,
);
if (branchCache?.commitTimestamp) {
const commitTime = DateTime.fromISO(
branchCache.commitTimestamp,
).toUTC();
if (commitTime > currentHourStart) {
soFarThisHour++;
}
} else {
// Fallback to SCM if not in cache
const updateDate = fallbackUpdateDates[branch.branchName];
if (updateDate && updateDate > currentHourStart) {
soFarThisHour++;
}
}
}
logger.debug(`${soFarThisHour} commits so far in this hour.`);
return soFarThisHour;
} catch (err) {
logger.error({ err }, 'Error checking commits per hour');
return 0;
}
});
}
export async function getConcurrentBranchesCount(
branches: BranchConfig[],
): Promise<number> {
return await instrument('getConcurrentBranchesCount', async () => {
let existingBranchCount = 0;
for (const branch of branches) {
if (await scm.branchExists(branch.branchName)) {
existingBranchCount++;
}
}
logger.debug(`${existingBranchCount} already existing branches found.`);
return existingBranchCount;
});
}