mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-27 07:50:47 +00:00
58771bb183
* refactor(oxlint): enable func-style Enable the built-in func-style rule as ["warn", "declaration"] so that named functions are written as function declarations instead of arrow or function expressions assigned to variables, matching the convention in docs/development/best-practices.md (Functions section). Inline arrow callbacks are unaffected; prefer-arrow-callback continues to cover the callback direction. Severity is "warn" instead of "error" as a fallback: a repo-wide run found 196 pre-existing violations (70 in spec files, 126 in production code), exceeding the 80-violation fix threshold for this change, so no violations were fixed here. Follow-up work can convert the offenders (largest clusters: lib/modules/versioning/ruby, lib/util/cache/package, lib/modules/versioning/maven, lib/modules/versioning/gradle) and then raise the rule to "error". Note oxlint 1.72 also supports an allowTypeAnnotation option if typed consts should remain arrows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linter): enforce func-style at error * refactor(func-style): convert const-arrow functions to declarations in with-cache spec and ruby versioning * refactor(func-style): convert const-arrow functions to declarations in maven/gradle modules * refactor(func-style): convert const-arrow functions to declarations in remaining versioning modules * refactor(func-style): convert const-arrow functions to declarations in platform modules Discriminated-union closures (ensureCommentRemoval byTopic/byContent helpers in github, gitlab, bitbucket, bitbucket-server) needed the narrowed field passed explicitly as a parameter, since function declarations are hoisted and lose the outer if-narrowing that inline arrow-function closures previously relied on. * refactor(func-style): convert const-arrow functions to declarations across util/manager/datasource modules One case (logger/renovate-logger.ts logOnceFn) is kept as an arrow function with a documented oxlint-disable: it is stored on this.logger.once[level] and relies on lexically-bound this, which a hoisted function declaration would not preserve. * refactor(func-style): convert remaining const-arrow test helpers to declarations get-updated.spec.ts: hoisted expectPipCompilePackageAndLockFile to module scope (it only closes over the module-level pipCompile mock) and added a documented oxlint-disable for vitest/expect-expect on its caller test, since oxlint's expect-expect check does not trace assertions through an externally-defined helper function. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
157 lines
4.0 KiB
TypeScript
157 lines
4.0 KiB
TypeScript
import { isUndefined } from '@sindresorhus/is';
|
|
import { DateTime } from 'luxon';
|
|
import { GlobalConfig } from '../../../config/global.ts';
|
|
import { logger } from '../../../logger/index.ts';
|
|
import { acquireLock } from '../../mutex.ts';
|
|
import * as packageCache from './index.ts';
|
|
import { resolveTtlValues } from './ttl.ts';
|
|
import type { CachedRecord, PackageCacheNamespace } from './types.ts';
|
|
|
|
interface CachedOptions {
|
|
/**
|
|
* The cache namespace.
|
|
*/
|
|
namespace: PackageCacheNamespace;
|
|
|
|
/**
|
|
* The cache key.
|
|
*/
|
|
key: string;
|
|
|
|
/**
|
|
* The TTL (or expiry) of the key in minutes.
|
|
* @default 30
|
|
*/
|
|
ttlMinutes?: number;
|
|
|
|
/**
|
|
* Whether caching is enabled for this call.
|
|
* When false, the function is called directly without caching.
|
|
* @default true
|
|
*/
|
|
cacheable?: boolean;
|
|
|
|
/**
|
|
* Whether a cache result is safe to use and store for this call.
|
|
* @default value !== undefined
|
|
*/
|
|
shouldCacheResult?: (value: unknown) => boolean;
|
|
|
|
/**
|
|
* Enable extended hard TTL for graceful degradation.
|
|
* When true and upstream errors occur, stale cached data is returned.
|
|
* @default false
|
|
*/
|
|
fallback?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Caches the result of an async function.
|
|
*
|
|
* @param options - Cache options
|
|
* @param fn - The async function to cache
|
|
* @returns The cached or fresh result
|
|
*/
|
|
export async function withCache<T>(
|
|
options: CachedOptions,
|
|
fn: () => T | Promise<T>,
|
|
): Promise<T> {
|
|
const {
|
|
namespace,
|
|
key,
|
|
ttlMinutes = 30,
|
|
cacheable = true,
|
|
fallback = false,
|
|
} = options;
|
|
|
|
const cachePrivatePackages = GlobalConfig.get('cachePrivatePackages');
|
|
const isCacheable = cachePrivatePackages || cacheable;
|
|
if (!isCacheable) {
|
|
return fn();
|
|
}
|
|
|
|
function defaultShouldCacheResult(_value: unknown): boolean {
|
|
return true;
|
|
}
|
|
const shouldCacheResult =
|
|
options.shouldCacheResult ?? defaultShouldCacheResult;
|
|
|
|
// istanbul ignore if -- TODO: add test #40625
|
|
if (!namespace || !key) {
|
|
return fn();
|
|
}
|
|
|
|
const cacheKey = `cache-decorator:${key}`;
|
|
|
|
// prevent concurrent processing and cache writes
|
|
const releaseLock = await acquireLock(cacheKey, namespace);
|
|
|
|
try {
|
|
const cachedRecord = await packageCache.get<CachedRecord>(
|
|
namespace,
|
|
cacheKey,
|
|
);
|
|
|
|
const { softTtlMinutes, hardTtlMinutes: resolvedHardTtl } =
|
|
resolveTtlValues(namespace, ttlMinutes);
|
|
|
|
// The separation between "soft" and "hard" TTL allows us to treat
|
|
// data as obsolete according to the "soft" TTL while physically storing it
|
|
// according to the "hard" TTL.
|
|
//
|
|
// This helps us return obsolete data in case of upstream server errors,
|
|
// which is more useful than throwing exceptions ourselves.
|
|
//
|
|
// The `fallback` option controls whether this extended TTL behavior is used.
|
|
// When false, the "soft" just equals the "hard" ttl.
|
|
const hardTtlMinutes = fallback ? resolvedHardTtl : softTtlMinutes;
|
|
|
|
let fallbackValue: unknown;
|
|
if (cachedRecord && shouldCacheResult(cachedRecord.value)) {
|
|
const now = DateTime.local();
|
|
const cachedAt = DateTime.fromISO(cachedRecord.cachedAt);
|
|
|
|
const softDeadline = cachedAt.plus({ minutes: softTtlMinutes });
|
|
if (now < softDeadline) {
|
|
return cachedRecord.value as T;
|
|
}
|
|
|
|
const hardDeadline = cachedAt.plus({ minutes: hardTtlMinutes });
|
|
if (now < hardDeadline) {
|
|
fallbackValue = cachedRecord.value;
|
|
}
|
|
}
|
|
|
|
let newValue: T;
|
|
try {
|
|
newValue = await fn();
|
|
} catch (err) {
|
|
if (!isUndefined(fallbackValue)) {
|
|
logger.debug(
|
|
{ err },
|
|
'Package cache: callback error, returning stale data',
|
|
);
|
|
return fallbackValue as T;
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
if (!isUndefined(newValue) && shouldCacheResult(newValue)) {
|
|
const newRecord: CachedRecord = {
|
|
cachedAt: DateTime.local().toISO(),
|
|
value: newValue,
|
|
};
|
|
await packageCache.setWithRawTtl(
|
|
namespace,
|
|
cacheKey,
|
|
newRecord,
|
|
hardTtlMinutes,
|
|
);
|
|
}
|
|
|
|
return newValue;
|
|
} finally {
|
|
releaseLock();
|
|
}
|
|
}
|