mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-27 07:50:47 +00:00
16ef1715e9
* feat(lint): add renovate/prefer-is-helpers and renovate/prefer-is-object rules Flag ad-hoc type checks that have @sindresorhus/is equivalents: - .filter(Boolean) -> .filter(isTruthy) - typeof x === 'string' -> isString(x) - x === null || x === undefined -> isNullOrUndefined(x) (and negation) The typeof x === 'object' shape lives in a separate suggestion-only rule (renovate/prefer-is-object, kept at warn) because isObject()/isPlainObject() are not drop-in equivalents: null, arrays and functions behave differently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: fix renovate/prefer-is-helpers violations Replace ad-hoc type checks with @sindresorhus/is helpers across lib/: - 13x .filter(Boolean) -> .filter(isTruthy) - 36x typeof x ==/!== 'string' -> isString(x) / !isString(x) - 6x x === null || x === undefined -> isNullOrUndefined(x) (and negation) Promote renovate/prefer-is-helpers to error now that the codebase is clean. renovate/prefer-is-object stays at warn: its 15 remaining sites guard parsed YAML/JSON or unknown values where isObject()/isPlainObject() are not provably equivalent (null/array/function semantics). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: enforce renovate/prefer-is-object at error level Resolve the 15 remaining warnings and raise the rule from warn to error: - Migrate 10 sites to isObject() where the guarded value provably cannot be a function (parsed YAML/JSON, typed exec command arguments, or test-controlled fixtures): helm-values, woodpecker, bun, pre-commit managers, exec utils, file package cache, and the github http spec. In the bun manager this also fixes a latent crash where a null `workspaces` value would throw on the `in` operator. - Keep 5 sites as reasoned oxlint-disable comments where excluding functions is load-bearing: the byte-exact fingerprint canonicalizer (2), the circularity walker, the zod result guard, and the generic hasKey helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
294 lines
8.7 KiB
TypeScript
294 lines
8.7 KiB
TypeScript
import { isString } from '@sindresorhus/is';
|
|
import type {
|
|
BranchSummary,
|
|
CommitResult,
|
|
DefaultLogFields,
|
|
DiffResult,
|
|
LogOptions,
|
|
LogResult,
|
|
Options,
|
|
RemoteWithRefs,
|
|
RemoteWithoutRefs,
|
|
Response,
|
|
SimpleGit,
|
|
StatusResult,
|
|
TaskOptions,
|
|
VersionResult,
|
|
} from 'simple-git';
|
|
import { instrument } from '../../instrumentation/index.ts';
|
|
|
|
import type { RenovateSpanOptions } from '../../instrumentation/types.ts';
|
|
import {
|
|
ATTR_VCS_GIT_OPERATION_TYPE,
|
|
ATTR_VCS_GIT_SUBCOMMAND,
|
|
} from '../../instrumentation/types.ts';
|
|
import type { GitOperationType } from './types.ts';
|
|
|
|
function isGitOperationType(
|
|
subcommand: string,
|
|
): subcommand is GitOperationType {
|
|
return knownGitOperationTypesBySubcommand.includes(
|
|
subcommand as GitOperationType,
|
|
);
|
|
}
|
|
|
|
function gitOperationTypeForSubcommand(subcommand: string): GitOperationType {
|
|
let operationType: GitOperationType = 'other';
|
|
if (!isGitOperationType(subcommand)) {
|
|
if (subcommand === 'update-index') {
|
|
operationType = 'plumbing';
|
|
}
|
|
|
|
return operationType;
|
|
}
|
|
|
|
return subcommand;
|
|
}
|
|
|
|
/** single-command prefixes that correspond to an operation type */
|
|
const knownGitOperationTypesBySubcommand: GitOperationType[] = [
|
|
'branch',
|
|
'checkout',
|
|
'clean',
|
|
'clone',
|
|
'commit',
|
|
'fetch',
|
|
'merge',
|
|
'pull',
|
|
'push',
|
|
'reset',
|
|
'submodule',
|
|
];
|
|
|
|
/** helper method for instrumentation of Git operations */
|
|
export function prepareInstrumentation(
|
|
subcommand: string,
|
|
options: RenovateSpanOptions = {},
|
|
): {
|
|
spanName: string;
|
|
options: RenovateSpanOptions;
|
|
} {
|
|
const operationType = gitOperationTypeForSubcommand(subcommand);
|
|
|
|
options.attributes ??= {};
|
|
options.attributes[ATTR_VCS_GIT_OPERATION_TYPE] = operationType;
|
|
options.attributes[ATTR_VCS_GIT_SUBCOMMAND] = subcommand;
|
|
|
|
return {
|
|
spanName: `git ${subcommand}`,
|
|
options,
|
|
};
|
|
}
|
|
|
|
export function instrumentGit(git: SimpleGit): InstrumentedSimpleGit {
|
|
return new InstrumentedSimpleGit(git);
|
|
}
|
|
|
|
/**
|
|
* A wrapper for SimpleGit, which is instrumented and adds an ATTR_VCS_GIT_SUBCOMMAND and ATTR_VCS_GIT_OPERATION_TYPE attribute to all calls.
|
|
*
|
|
* We purposefully use a reduced set of methods, with simpler function parameters, to reduce the complexity needed to instrument the methods that we actually need.
|
|
*
|
|
* @see SimpleGit
|
|
*/
|
|
export class InstrumentedSimpleGit {
|
|
private git: SimpleGit;
|
|
|
|
constructor(git: SimpleGit) {
|
|
this.git = git;
|
|
}
|
|
|
|
version(): Response<VersionResult> {
|
|
return this.git.version();
|
|
}
|
|
|
|
raw(args: string[]): Response<string> {
|
|
const { spanName, options } = prepareInstrumentation(args[0]);
|
|
|
|
return instrument(spanName, () => this.git.raw(args), options);
|
|
}
|
|
|
|
async checkout(whatOrOptions: string | TaskOptions): Promise<string> {
|
|
const { spanName, options } = prepareInstrumentation('checkout');
|
|
|
|
return await instrument(
|
|
spanName,
|
|
async () => {
|
|
if (isString(whatOrOptions)) {
|
|
return await this.git.checkout(whatOrOptions);
|
|
}
|
|
return await this.git.checkout(whatOrOptions);
|
|
},
|
|
options,
|
|
);
|
|
}
|
|
|
|
async checkoutBranch(branch: string, startPoint: string): Promise<void> {
|
|
const { spanName, options } = prepareInstrumentation('checkout');
|
|
|
|
await instrument(
|
|
spanName,
|
|
() => this.git.checkoutBranch(branch, startPoint),
|
|
options,
|
|
);
|
|
}
|
|
async branch(args: string[]): Promise<BranchSummary> {
|
|
const { spanName, options } = prepareInstrumentation('branch');
|
|
|
|
return await instrument(spanName, () => this.git.branch(args), options);
|
|
}
|
|
async branchLocal(): Promise<BranchSummary> {
|
|
const { spanName, options } = prepareInstrumentation('branch');
|
|
|
|
return await instrument(spanName, () => this.git.branchLocal(), options);
|
|
}
|
|
async add(files: string | string[]): Promise<string> {
|
|
const { spanName, options } = prepareInstrumentation('add');
|
|
|
|
return await instrument(spanName, () => this.git.add(files), options);
|
|
}
|
|
async addConfig(key: string, value: string): Promise<void> {
|
|
const { spanName, options } = prepareInstrumentation('config');
|
|
|
|
await instrument(spanName, () => this.git.addConfig(key, value), options);
|
|
}
|
|
async rm(files: string | string[]): Promise<void> {
|
|
const { spanName, options } = prepareInstrumentation('rm');
|
|
|
|
await instrument(spanName, () => this.git.rm(files), options);
|
|
}
|
|
async reset(modeOrOptions: any): Promise<void> {
|
|
const { spanName, options } = prepareInstrumentation('reset');
|
|
|
|
await instrument(spanName, () => this.git.reset(modeOrOptions), options);
|
|
}
|
|
async merge(args: string[]): Promise<void> {
|
|
const { spanName, options } = prepareInstrumentation('merge');
|
|
|
|
await instrument(spanName, () => this.git.merge(args), options);
|
|
}
|
|
async push(...args: any[]): Promise<any> {
|
|
const { spanName, options } = prepareInstrumentation('push');
|
|
|
|
return await instrument(spanName, () => this.git.push(...args), options);
|
|
}
|
|
async pull(args: string[]): Promise<any> {
|
|
const { spanName, options } = prepareInstrumentation('pull');
|
|
|
|
return await instrument(spanName, () => this.git.pull(args), options);
|
|
}
|
|
async fetch(args: string[]): Promise<any> {
|
|
const { spanName, options } = prepareInstrumentation('fetch');
|
|
|
|
return await instrument(spanName, () => this.git.fetch(args), options);
|
|
}
|
|
async clone(repo: string, dir: string, options: string[]): Promise<void> {
|
|
const { spanName, options: spanOptions } = prepareInstrumentation('clone');
|
|
|
|
await instrument(
|
|
spanName,
|
|
() => this.git.clone(repo, dir, options),
|
|
spanOptions,
|
|
);
|
|
}
|
|
commit(
|
|
message: string | string[],
|
|
files?: string[],
|
|
options?: Options,
|
|
): Promise<CommitResult> {
|
|
const { spanName, options: spanOptions } = prepareInstrumentation('clone');
|
|
|
|
return instrument(
|
|
spanName,
|
|
() => this.git.commit(message, files, options),
|
|
spanOptions,
|
|
);
|
|
}
|
|
status(args?: string[]): Response<StatusResult> {
|
|
const { spanName, options } = prepareInstrumentation('status');
|
|
|
|
return instrument(spanName, () => this.git.status(args), options);
|
|
}
|
|
async log<T = DefaultLogFields>(
|
|
options?: TaskOptions | LogOptions<T>,
|
|
): Promise<LogResult<T>> {
|
|
const { spanName, options: spanOptions } = prepareInstrumentation('log');
|
|
|
|
return await instrument(spanName, () => this.git.log(options), spanOptions);
|
|
}
|
|
async revparse(optionOrOptions: string | TaskOptions): Promise<string> {
|
|
const { spanName, options } = prepareInstrumentation('log');
|
|
|
|
return await instrument(
|
|
spanName,
|
|
async () => {
|
|
if (isString(optionOrOptions)) {
|
|
return await this.git.revparse(optionOrOptions);
|
|
}
|
|
return await this.git.revparse(optionOrOptions);
|
|
},
|
|
options,
|
|
);
|
|
}
|
|
async show(args: string[]): Promise<string> {
|
|
const { spanName, options } = prepareInstrumentation('show');
|
|
|
|
return await instrument(spanName, () => this.git.show(args), options);
|
|
}
|
|
async diff(args: string[]): Promise<string> {
|
|
const { spanName, options } = prepareInstrumentation('diff');
|
|
|
|
return await instrument(spanName, () => this.git.diff(args), options);
|
|
}
|
|
|
|
async diffSummary(options: TaskOptions): Promise<DiffResult> {
|
|
const { spanName, options: spanOptions } = prepareInstrumentation('diff');
|
|
|
|
return await instrument(
|
|
spanName,
|
|
() => this.git.diffSummary(options),
|
|
spanOptions,
|
|
);
|
|
}
|
|
async getRemotes(
|
|
verbose?: boolean,
|
|
): Promise<RemoteWithRefs[] | RemoteWithoutRefs[]> {
|
|
const { spanName, options } = prepareInstrumentation('remote');
|
|
|
|
return await instrument(
|
|
spanName,
|
|
async () => {
|
|
if (verbose === true) {
|
|
return await instrument('other', () => this.git.getRemotes(true));
|
|
}
|
|
return await instrument('other', () => this.git.getRemotes());
|
|
},
|
|
options,
|
|
);
|
|
}
|
|
async addRemote(name: string, repo: string): Promise<void> {
|
|
const { spanName, options } = prepareInstrumentation('remote');
|
|
|
|
await instrument(spanName, () => this.git.addRemote(name, repo), options);
|
|
}
|
|
env(env: Record<string, string>): this {
|
|
this.git = this.git.env(env);
|
|
return this;
|
|
}
|
|
async catFile(args: string[]): Promise<string> {
|
|
const { spanName, options } = prepareInstrumentation('remote');
|
|
|
|
return await instrument(spanName, () => this.git.catFile(args), options);
|
|
}
|
|
async listRemote(args: string[]): Promise<string> {
|
|
const { spanName, options } = prepareInstrumentation('ls-remote');
|
|
|
|
return await instrument(spanName, () => this.git.listRemote(args), options);
|
|
}
|
|
async submoduleUpdate(args: string[]): Promise<void> {
|
|
const { spanName, options } = prepareInstrumentation('submodule');
|
|
|
|
await instrument(spanName, () => this.git.submoduleUpdate(args), options);
|
|
}
|
|
}
|