mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(ui): prevent generated locale rebase conflicts (#109393)
* ci(ui): make locale refresh bot-owned * test(ui): keep locale gate coverage scoped * fix(ci): preserve generated PR merge policy * fix(ci): wait for generated PR head convergence * fix(ci): retry transient guard API failures * fix(ci): enforce locale isolation across CI events * fix(ci): harden generated PR publication * fix(ci): validate release gate merge tree * fix(ci): allow trusted main locale output
This commit is contained in:
committed by
GitHub
parent
172c63dc23
commit
067635cb51
@@ -98,6 +98,7 @@ export function githubApi(
|
||||
options?: {
|
||||
fetchImpl?: typeof fetch;
|
||||
responseMaxBodyBytes?: number;
|
||||
retryDelaysMs?: readonly number[];
|
||||
timeoutMs?: number;
|
||||
},
|
||||
): { request(path: string, options?: Record<string, unknown>): Promise<unknown> };
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { setTimeout as wait } from "node:timers/promises";
|
||||
import { readBoundedResponseText } from "../lib/bounded-response.mjs";
|
||||
|
||||
export const GITHUB_ERROR_BODY_MAX_BYTES = 64 * 1024;
|
||||
export const GITHUB_RESPONSE_BODY_MAX_BYTES = 4 * 1024 * 1024;
|
||||
export const GITHUB_API_REQUEST_TIMEOUT_MS = 30_000;
|
||||
|
||||
const githubApiRetryStatuses = new Set([502, 503, 504]);
|
||||
const githubApiRetryDelaysMs = [1_000, 2_000, 4_000];
|
||||
|
||||
export function guardTrustedActorCandidates({ pullRequest, event, currentHeadSha }) {
|
||||
const eventHeadSha = event?.pull_request?.head?.sha;
|
||||
const eventAfterSha = event?.after;
|
||||
@@ -230,6 +234,7 @@ function combineAbortSignals(signals) {
|
||||
export function createGitHubApi(token, options = {}) {
|
||||
const fetchImpl = options.fetchImpl ?? fetch;
|
||||
const timeoutMs = options.timeoutMs ?? GITHUB_API_REQUEST_TIMEOUT_MS;
|
||||
const retryDelaysMs = options.retryDelaysMs ?? githubApiRetryDelaysMs;
|
||||
const responseMaxBodyBytes = options.responseMaxBodyBytes ?? GITHUB_RESPONSE_BODY_MAX_BYTES;
|
||||
const baseHeaders = {
|
||||
accept: "application/vnd.github+json",
|
||||
@@ -238,8 +243,9 @@ export function createGitHubApi(token, options = {}) {
|
||||
"x-github-api-version": "2022-11-28",
|
||||
};
|
||||
const request = async (path, requestOptions = {}) => {
|
||||
const method = requestOptions.method ?? "GET";
|
||||
const method = (requestOptions.method ?? "GET").toUpperCase();
|
||||
const timeoutController = new AbortController();
|
||||
const requestSignal = combineAbortSignals([requestOptions.signal, timeoutController.signal]);
|
||||
let timeout;
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
timeout = setTimeout(() => {
|
||||
@@ -249,32 +255,43 @@ export function createGitHubApi(token, options = {}) {
|
||||
timeout.unref?.();
|
||||
});
|
||||
const operationPromise = (async () => {
|
||||
const response = await fetchImpl(`https://api.github.com${path}`, {
|
||||
...requestOptions,
|
||||
signal: combineAbortSignals([requestOptions.signal, timeoutController.signal]),
|
||||
headers: { ...baseHeaders, ...requestOptions.headers },
|
||||
});
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
if (!response.ok) {
|
||||
let errorText;
|
||||
try {
|
||||
errorText = await readBoundedGitHubErrorText(response, GITHUB_ERROR_BODY_MAX_BYTES, {
|
||||
signal: timeoutController.signal,
|
||||
timeoutPromise,
|
||||
});
|
||||
} catch (bodyError) {
|
||||
errorText = bodyError instanceof Error ? bodyError.message : String(bodyError);
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
const response = await fetchImpl(`https://api.github.com${path}`, {
|
||||
...requestOptions,
|
||||
signal: requestSignal,
|
||||
headers: { ...baseHeaders, ...requestOptions.headers },
|
||||
});
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
const error = new Error(`${response.status} ${response.statusText}: ${errorText}`);
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
if (!response.ok) {
|
||||
if (
|
||||
(method === "GET" || method === "HEAD") &&
|
||||
githubApiRetryStatuses.has(response.status) &&
|
||||
attempt < retryDelaysMs.length
|
||||
) {
|
||||
await response.body?.cancel().catch(() => {});
|
||||
await wait(retryDelaysMs[attempt], undefined, { signal: requestSignal });
|
||||
continue;
|
||||
}
|
||||
let errorText;
|
||||
try {
|
||||
errorText = await readBoundedGitHubErrorText(response, GITHUB_ERROR_BODY_MAX_BYTES, {
|
||||
signal: timeoutController.signal,
|
||||
timeoutPromise,
|
||||
});
|
||||
} catch (bodyError) {
|
||||
errorText = bodyError instanceof Error ? bodyError.message : String(bodyError);
|
||||
}
|
||||
const error = new Error(`${response.status} ${response.statusText}: ${errorText}`);
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
}
|
||||
return await readBoundedGitHubJson(response, responseMaxBodyBytes, {
|
||||
signal: timeoutController.signal,
|
||||
timeoutPromise,
|
||||
});
|
||||
}
|
||||
return await readBoundedGitHubJson(response, responseMaxBodyBytes, {
|
||||
signal: timeoutController.signal,
|
||||
timeoutPromise,
|
||||
});
|
||||
})();
|
||||
operationPromise.catch(() => {});
|
||||
try {
|
||||
|
||||
@@ -62,7 +62,12 @@ export function findTrustedSecuritySensitiveGuardActor(options: {
|
||||
}): Promise<{ login: string; reason: string } | null>;
|
||||
export function githubApi(
|
||||
token: string,
|
||||
options?: { fetchImpl?: typeof fetch; responseMaxBodyBytes?: number; timeoutMs?: number },
|
||||
options?: {
|
||||
fetchImpl?: typeof fetch;
|
||||
responseMaxBodyBytes?: number;
|
||||
retryDelaysMs?: readonly number[];
|
||||
timeoutMs?: number;
|
||||
},
|
||||
): { request(path: string, options?: Record<string, unknown>): Promise<unknown> };
|
||||
export function readBoundedGitHubErrorText(
|
||||
response: Response,
|
||||
|
||||
Reference in New Issue
Block a user