Files
renovate/lib/modules/platform/codecommit/codecommit-client.ts
Sebastian Poxhofer a1971afa78 fix(linters): add linting rule to enforce usage of our regEx utility (#44690)
* ci(linter): add renovate/require-regex-util rule

Adds a custom oxlint rule that flags raw RegExp usage in lib/
production code: any `new RegExp(...)` call and any regex literal
that is not the direct first argument of `regEx(...)`. The regEx()
helper from lib/util/regex.ts uses the RE2 engine for ReDoS-safe
matching and caches compiled patterns. lib/util/regex.ts itself is
exempt.

The rule is disabled for **/*.spec.ts, test/**, and tools/** via
config overrides, because vitest APIs such as expect.stringMatching
need bare RegExp instances (a past commit reverted regEx() there).

Severity is set to "warn" instead of "error": the repo currently has
197 pre-existing violations (above the agreed mechanical-fix
threshold of 80), and a number of them are deliberately
RE2-incompatible patterns (lookahead/lookbehind/named backreferences,
see TODO #12872/#12875 markers in e.g. lib/modules/versioning/rez/
pattern.ts and lib/modules/manager/dockerfile/extract.ts) that need
per-site judgment before they can be wrapped in regEx() or suppressed
with a justified disable comment. No violation fixes are included in
this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(linter): enforce renovate/require-regex-util at error

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: use regEx() for regex literals and RegExp construction in lib

Burn down all renovate/require-regex-util violations (197):

- wrap RE2-compatible regex literals and RegExp constructions in regEx()
- rewrite lookaround patterns where behavior is provably preserved:
  goproxy separator split, sbt-package link extraction, leiningen
  comment stripping and keyword search, npm GitHub owner validation
  (unit-count bound replaced by an explicit length check)
- keep native RegExp with disable comments where RE2 cannot be used:
  moo / good-enough-parser lexer patterns (moo rejects RE2 instances),
  MigrationsService propertyName (matched via isRegExp, which RE2
  instances fail), rez named backreferences and lookaheads, GitHub
  markdown link negative lookbehind, emoji astral-plane matching
- drop stale TODO #12875 comments for patterns current re2 handles

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: reduce require-regex-util disable comments

Rewrite the github massage-markdown-links URL regex to capture an
optional `api.` prefix in a named group (skipped in code) instead of
relying on a negative lookbehind, so both the base and global variants
can use regEx()/RE2 and their two disable comments are removed. Add
spec cases for api-prefixed URLs that match the URL shape.

Consolidate the four related line disables in rez/pattern.ts (bound and
range patterns emulating Python conditional patterns via named
backreferences and lookaheads, which RE2 cannot express) into a single
block disable/enable pair, matching the treatment of the lexer files.

Disable comment count drops from 13 to 8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(datasource/sbt-package): use named capture group in link regex

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(manager/cake): restore original multiLineComment regex spelling

The [^] -> [\\s\\S] rewrite was only needed for an RE2 wrap that was
later reverted; keep the diff minimal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(datasource/go): skip empty GOPROXY segments

The switch from a lookahead split to a capture-group split (needed because
RE2 has no lookahead support) changed how empty GOPROXY segments parse:
`a|,b` yielded `{ url: ',', fallback: '|' }` before and
`{ url: '', fallback: ',' }` after. The comma fallback only continues on
404/410, so a failing proxy ahead of an empty segment silently stopped the
lookup instead of falling through to the remaining proxies.

Empty segments carry no url to query, so drop them.

* refactor(datasource/go): use isTruthy for empty segment filter

* fix(datasource/go): filter empty segments before mapping

Filtering after the map broke the contextual typing from the
GoproxyItem[] annotation, widening fallback to string.

* refactor(platform/github): use regEx() for merge queue query stripping

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 14:30:46 +00:00

325 lines
9.1 KiB
TypeScript

import type {
CreatePullRequestApprovalRuleInput,
CreatePullRequestApprovalRuleOutput,
CreatePullRequestInput,
CreatePullRequestOutput,
DeleteCommentContentInput,
DeleteCommentContentOutput,
GetCommentsForPullRequestInput,
GetCommentsForPullRequestOutput,
GetFileInput,
GetFileOutput,
GetPullRequestInput,
GetPullRequestOutput,
GetRepositoryInput,
GetRepositoryOutput,
ListPullRequestsInput,
ListPullRequestsOutput,
ListRepositoriesInput,
ListRepositoriesOutput,
PostCommentForPullRequestInput,
PostCommentForPullRequestOutput,
RepositoryMetadata,
UpdateCommentInput,
UpdateCommentOutput,
UpdatePullRequestDescriptionInput,
UpdatePullRequestDescriptionOutput,
UpdatePullRequestStatusInput,
UpdatePullRequestStatusOutput,
UpdatePullRequestTitleInput,
UpdatePullRequestTitleOutput,
} from '@aws-sdk/client-codecommit';
import {
CodeCommitClient,
CreatePullRequestApprovalRuleCommand,
CreatePullRequestCommand,
DeleteCommentContentCommand,
GetCommentsForPullRequestCommand,
GetFileCommand,
GetPullRequestCommand,
GetRepositoryCommand,
ListPullRequestsCommand,
ListRepositoriesCommand,
PostCommentForPullRequestCommand,
PullRequestStatusEnum,
UpdateCommentCommand,
UpdatePullRequestDescriptionCommand,
UpdatePullRequestStatusCommand,
UpdatePullRequestTitleCommand,
} from '@aws-sdk/client-codecommit';
import { isString } from '@sindresorhus/is';
import * as aws4 from 'aws4';
import { REPOSITORY_UNINITIATED } from '../../../constants/error-messages.ts';
import { logger } from '../../../logger/index.ts';
import { getEnv } from '../../../util/env.ts';
import { regEx } from '../../../util/regex.ts';
let codeCommitClient: CodeCommitClient;
export function buildCodeCommitClient(): void {
if (!codeCommitClient) {
codeCommitClient = new CodeCommitClient({});
}
/* v8 ignore next -- unreachable: the client was just constructed above */
if (!codeCommitClient) {
throw new Error('Failed to initialize codecommit client');
}
}
export async function deleteComment(
commentId: string,
): Promise<DeleteCommentContentOutput> {
const input: DeleteCommentContentInput = {
commentId,
};
const cmd = new DeleteCommentContentCommand(input);
return await codeCommitClient.send(cmd);
}
export async function getPrComments(
pullRequestId: string,
): Promise<GetCommentsForPullRequestOutput> {
const input: GetCommentsForPullRequestInput = {
pullRequestId,
};
const cmd = new GetCommentsForPullRequestCommand(input);
return await codeCommitClient.send(cmd);
}
export async function updateComment(
commentId: string,
content: string,
): Promise<UpdateCommentOutput> {
const input: UpdateCommentInput = {
commentId,
content,
};
const cmd = new UpdateCommentCommand(input);
return await codeCommitClient.send(cmd);
}
export async function createPrComment(
pullRequestId: string,
repositoryName: string | undefined,
content: string,
beforeCommitId: string,
afterCommitId: string,
): Promise<PostCommentForPullRequestOutput> {
const input: PostCommentForPullRequestInput = {
pullRequestId,
repositoryName,
content,
afterCommitId,
beforeCommitId,
};
const cmd = new PostCommentForPullRequestCommand(input);
return await codeCommitClient.send(cmd);
}
// export async function fastForwardMerge(
// repositoryName: string,
// sourceCommitSpecifier: string,
// destinationReference: string
// ): Promise<MergeBranchesByFastForwardOutput> {
// const input: MergeBranchesByFastForwardInput = {
// repositoryName,
// sourceCommitSpecifier,
// destinationCommitSpecifier: destinationReference,
// targetBranch: destinationReference,
// };
// const cmd = new MergeBranchesByFastForwardCommand(input);
// return await codeCommitClient.send(cmd);
// }
// export async function squashMerge(
// repositoryName: string,
// sourceCommitSpecifier: string,
// destinationReference: string,
// commitMessage: string | undefined
// ): Promise<MergeBranchesBySquashOutput> {
// const input: MergeBranchesBySquashInput = {
// repositoryName,
// sourceCommitSpecifier,
// destinationCommitSpecifier: destinationReference,
// targetBranch: destinationReference,
// commitMessage,
// };
// const cmd = new MergeBranchesBySquashCommand(input);
// return await codeCommitClient.send(cmd);
// }
export async function updatePrStatus(
pullRequestId: string,
pullRequestStatus: PullRequestStatusEnum,
): Promise<UpdatePullRequestStatusOutput> {
const input: UpdatePullRequestStatusInput = {
pullRequestId,
pullRequestStatus,
};
const cmd = new UpdatePullRequestStatusCommand(input);
return await codeCommitClient.send(cmd);
}
export async function updatePrTitle(
prNo: string,
title: string,
): Promise<UpdatePullRequestTitleOutput> {
const input: UpdatePullRequestTitleInput = {
pullRequestId: `${prNo}`,
title,
};
const cmd = new UpdatePullRequestTitleCommand(input);
return await codeCommitClient.send(cmd);
}
export async function updatePrDescription(
pullRequestId: string,
description: string,
): Promise<UpdatePullRequestDescriptionOutput> {
const input: UpdatePullRequestDescriptionInput = {
pullRequestId,
description,
};
const cmd = new UpdatePullRequestDescriptionCommand(input);
return await codeCommitClient.send(cmd);
}
export async function createPr(
title: string,
description: string,
sourceReference: string,
destinationReference: string,
repositoryName: string | undefined,
): Promise<CreatePullRequestOutput> {
const input: CreatePullRequestInput = {
title,
description,
targets: [
{
sourceReference,
destinationReference,
repositoryName,
},
],
};
const cmd = new CreatePullRequestCommand(input);
return await codeCommitClient.send(cmd);
}
export async function getFile(
repositoryName: string | undefined,
filePath: string,
commitSpecifier: string | undefined,
): Promise<GetFileOutput> {
const input: GetFileInput = {
repositoryName,
filePath,
commitSpecifier,
};
const cmd: GetFileCommand = new GetFileCommand(input);
return await codeCommitClient.send(cmd);
}
export async function listPullRequests(
repositoryName: string,
): Promise<ListPullRequestsOutput> {
const input: ListPullRequestsInput = {
repositoryName,
pullRequestStatus: PullRequestStatusEnum.OPEN,
};
const cmd = new ListPullRequestsCommand(input);
return await codeCommitClient.send(cmd);
}
export async function getRepositoryInfo(
repository: string,
): Promise<GetRepositoryOutput> {
const input: GetRepositoryInput = {
repositoryName: `${repository}`,
};
const cmd = new GetRepositoryCommand(input);
return await codeCommitClient.send(cmd);
}
export async function getPr(
pullRequestId: string,
): Promise<GetPullRequestOutput | undefined> {
const input: GetPullRequestInput = {
pullRequestId,
};
const cmd = new GetPullRequestCommand(input);
let res;
try {
res = await codeCommitClient.send(cmd);
} catch (err) {
logger.debug({ err }, 'failed to get PR using prId');
}
return res;
}
export async function listRepositories(): Promise<ListRepositoriesOutput> {
const input: ListRepositoriesInput = {};
const cmd = new ListRepositoriesCommand(input);
return await codeCommitClient.send(cmd);
}
export async function createPrApprovalRule(
pullRequestId: string,
approvalRuleContent: string,
): Promise<CreatePullRequestApprovalRuleOutput> {
const input: CreatePullRequestApprovalRuleInput = {
approvalRuleContent,
approvalRuleName: 'Reviewers By Renovate',
pullRequestId,
};
const cmd = new CreatePullRequestApprovalRuleCommand(input);
return await codeCommitClient.send(cmd);
}
export function getCodeCommitUrl(
repoMetadata: RepositoryMetadata,
repoName: string,
): string {
logger.debug('get code commit url');
const env = getEnv();
if (!env.AWS_ACCESS_KEY_ID || !env.AWS_SECRET_ACCESS_KEY) {
if (repoMetadata.cloneUrlHttp) {
return repoMetadata.cloneUrlHttp;
}
// shouldn't reach here, but just in case
return `https://git-codecommit.${
env.AWS_REGION ?? 'us-east-1'
}.amazonaws.com/v1/repos/${repoName}`;
}
const signer = new aws4.RequestSigner({
service: 'codecommit',
host: `git-codecommit.${env.AWS_REGION ?? 'us-east-1'}.amazonaws.com`,
method: 'GIT',
path: `v1/repos/${repoName}`,
});
const dateTime = signer.getDateTime();
/* v8 ignore next -- defensive: the SigV4 signer always returns a datetime string */
if (!isString(dateTime)) {
throw new Error(REPOSITORY_UNINITIATED);
}
const token = `${dateTime}Z${signer.signature()}`;
let username = `${env.AWS_ACCESS_KEY_ID}${
env.AWS_SESSION_TOKEN ? `%${env.AWS_SESSION_TOKEN}` : ''
}`;
// massaging username with the session token,
/* v8 ignore next -- only hit when an AWS session token contains '/', not present in spec env */
if (username.includes('/')) {
username = username.replace(regEx(/\//g), '%2F');
}
return `https://${username}:${token}@git-codecommit.${
env.AWS_REGION ?? 'us-east-1'
}.amazonaws.com/v1/repos/${repoName}`;
}